Skip to content

Commit

Permalink
Merge pull request #488 from girder/etag
Browse files Browse the repository at this point in the history
Support etags to reduce data transfer.
  • Loading branch information
manthey authored Oct 20, 2020
2 parents e696944 + 662fbab commit 5dde8ca
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 12 deletions.
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

0 comments on commit 5dde8ca

Please sign in to comment.