Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support etags to reduce data transfer. #488

Merged
merged 1 commit into from
Oct 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

### Improvements
- Include ETag support in some Girder rest requests to reduce data transfer (#488)

### Changes
- Don't let bioformats handle pngs (#487)

Expand Down
42 changes: 30 additions & 12 deletions girder/girder_large_image/rest/tiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#############################################################################

import cherrypy
import hashlib
import math
import os
import re
Expand All @@ -33,6 +34,7 @@

from large_image.constants import TileInputUnits
from large_image.exceptions import TileGeneralException
from large_image.cache_util import strhash

from ..models.image_item import ImageItem
from .. import loadmodelcache
Expand Down Expand Up @@ -69,6 +71,27 @@ def _adjustParams(params):
params['encoding'] = 'JFIF'


def _handleETag(key, item, *args, **kwargs):
"""
Add or check an ETag header.

:param key: key for making a distinc etag.
:param item: item used for the item _id and updated timestamp.
:param *args, **kwargs: additional arguments for generating an etag.
"""
etag = hashlib.md5(strhash(key, str(item['_id']), *args, **kwargs).encode()).hexdigest()
setResponseHeader('ETag', etag)
conditions = [str(x) for x in cherrypy.request.headers.elements('If-Match') or []]
if conditions and not (conditions == ['*'] or etag in conditions):
raise cherrypy.HTTPError(
412, 'If-Match failed: ETag %r did not match %r' % (etag, conditions))
conditions = [str(x) for x in cherrypy.request.headers.elements('If-None-Match') or []]
if conditions == ['*'] or etag in conditions:
raise cherrypy.HTTPRedirect([], 304)
# Explicitly set a max-ago to recheck the cahe after a while
setResponseHeader('Cache-control', 'max-age=600')


class TilesItemResource(ItemResource):

def __init__(self, apiRoot):
Expand Down Expand Up @@ -380,10 +403,7 @@ def getTile(self, itemId, z, x, y, params):
_adjustParams(params)
item = loadmodelcache.loadModel(
self, 'item', id=itemId, allowCookie=True, level=AccessType.READ)
# Explicitly set a expires time to encourage browsers to cache this for
# a while.
setResponseHeader('Expires', cherrypy.lib.httputil.HTTPDate(
cherrypy.serving.response.time + 600))
_handleETag('getTile', item, z, x, y, params)
redirect = params.get('redirect', False)
if redirect not in ('any', 'exact', 'encoding'):
redirect = False
Expand Down Expand Up @@ -417,10 +437,7 @@ def getTileWithFrame(self, itemId, frame, z, x, y, params):
_adjustParams(params)
item = loadmodelcache.loadModel(
self, 'item', id=itemId, allowCookie=True, level=AccessType.READ)
# Explicitly set a expires time to encourage browsers to cache this for
# a while.
setResponseHeader('Expires', cherrypy.lib.httputil.HTTPDate(
cherrypy.serving.response.time + 600))
_handleETag('getTileWithFrame', item, frame, z, x, y, params)
redirect = params.get('redirect', False)
if redirect not in ('any', 'exact', 'encoding'):
redirect = False
Expand Down Expand Up @@ -467,10 +484,7 @@ def getDZITile(self, item, level, xandy, params):
if overlap < 0:
raise RestException('Invalid overlap', code=400)
x, y = [int(xy) for xy in xandy.split('.')[0].split('_')]
# Explicitly set a expires time to encourage browsers to cache this for
# a while.
setResponseHeader('Expires', cherrypy.lib.httputil.HTTPDate(
cherrypy.serving.response.time + 600))
_handleETag('getDZITile', item, level, xandy, params)
metadata = self.imageItemModel.getMetadata(item, **params)
level = int(level)
maxlevel = int(math.ceil(math.log(max(
Expand Down Expand Up @@ -571,6 +585,7 @@ def getTilesThumbnail(self, item, params):
('style', str),
('contentDisposition', str),
])
_handleETag('getTilesThumbnail', item, params)
try:
result = self.imageItemModel.getThumbnail(item, **params)
except TileGeneralException as e:
Expand Down Expand Up @@ -691,6 +706,7 @@ def getTilesRegion(self, item, params):
('style', str),
('contentDisposition', str),
])
_handleETag('getTilesRegion', item, params)
try:
regionData, regionMime = self.imageItemModel.getRegion(
item, **params)
Expand Down Expand Up @@ -807,6 +823,7 @@ def getHistogram(self, item, params):
('rangeMax', int),
('density', bool),
])
_handleETag('getHistogram', item, params)
histRange = None
if 'rangeMin' in params or 'rangeMax' in params:
histRange = [params.pop('rangeMin', 0), params.pop('rangeMax', 256)]
Expand Down Expand Up @@ -871,6 +888,7 @@ def getAssociatedImage(self, itemId, image, params):
('style', str),
('contentDisposition', str),
])
_handleETag('getAssociatedImage', item, image, params)
try:
result = self.imageItemModel.getAssociatedImage(item, image, **params)
except TileGeneralException as e:
Expand Down