From 4a238491f055ae4116bb0607d8ae31fa9e006974 Mon Sep 17 00:00:00 2001 From: David Manthey Date: Wed, 5 Oct 2022 13:29:09 -0400 Subject: [PATCH] Support etags on annotations to improve browser caching. --- CHANGELOG.md | 1 + girder/girder_large_image/rest/tiles.py | 8 +++++-- .../rest/annotation.py | 22 ++++++++++--------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34ae69ca2..28ec13baa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Show configured item lists even if there are no large images ([#972](../../pull/972)) - Add metadata and annotation metadata search modes to Girder ([#974](../../pull/974)) - Add the ability to show annotation metadata in item annotation lists ([#977](../../pull/977)) +- Support ETAG in annotation rest responses for better browser caching ([#978](../../pull/978)) ## 1.17.0 diff --git a/girder/girder_large_image/rest/tiles.py b/girder/girder_large_image/rest/tiles.py index a7a58ef86..800b30d14 100644 --- a/girder/girder_large_image/rest/tiles.py +++ b/girder/girder_large_image/rest/tiles.py @@ -83,9 +83,13 @@ def _handleETag(key, item, *args, **kwargs): :param key: key for making a distinc etag. :param item: item used for the item _id and updated timestamp. + :param max_age: the maximum cache duration. :param *args, **kwargs: additional arguments for generating an etag. """ - etag = hashlib.md5(strhash(key, str(item['_id']), *args, **kwargs).encode()).hexdigest() + max_age = kwargs.get('max_age', 3600) + id = str(item['_id']) + date = item.get('updated', item.get('created')) + etag = hashlib.md5(strhash(key, id, date, *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): @@ -95,7 +99,7 @@ def _handleETag(key, item, *args, **kwargs): if conditions == ['*'] or etag in conditions: raise cherrypy.HTTPRedirect([], 304) # Explicitly set a max-age to recheck the cache after a while - setResponseHeader('Cache-control', 'max-age=3600') + setResponseHeader('Cache-control', f'max-age={max_age}') def _pickleParams(params): diff --git a/girder_annotation/girder_large_image_annotation/rest/annotation.py b/girder_annotation/girder_large_image_annotation/rest/annotation.py index 6efab9071..1c5aa8163 100644 --- a/girder_annotation/girder_large_image_annotation/rest/annotation.py +++ b/girder_annotation/girder_large_image_annotation/rest/annotation.py @@ -21,6 +21,7 @@ import cherrypy import orjson from bson.objectid import ObjectId +from girder_large_image.rest.tiles import _handleETag from girder import logger from girder.api import access @@ -168,24 +169,23 @@ def getAnnotationSchema(self, params): @access.public(cookie=True) def getAnnotation(self, id, params): user = self.getCurrentUser() - return self._getAnnotation(user, id, params) + annotation = Annotation().load( + id, region=params, user=user, level=AccessType.READ, getElements=False) + _handleETag('getAnnotation', annotation, params, max_age=86400 * 30) + if annotation is None: + raise RestException('Annotation not found', 404) + return self._getAnnotation(annotation, params) - def _getAnnotation(self, user, id, params): + def _getAnnotation(self, annotation, params): """ Get a generator function that will yield the json of an annotation. - :param user: the user that needs read access on the annotation and its - parent item. - :param id: the annotation id. + :param annotation: the annotation document without elements. :param params: paging and region parameters for the annotation. :returns: a function that will return a generator. """ # Set the response time limit to a very long value setResponseTimeLimit(86400) - annotation = Annotation().load( - id, region=params, user=user, level=AccessType.READ, getElements=False) - if annotation is None: - raise RestException('Annotation not found', 404) # Ensure that we have read access to the parent item. We could fail # faster when there are permissions issues if we didn't load the # annotation elements before checking the item access permissions. @@ -509,7 +509,9 @@ def generateResult(): if not first: yield b',\n' try: - annotationGenerator = self._getAnnotation(user, annotation['_id'], {})() + annotation = Annotation().load( + annotation['_id'], user=user, level=AccessType.READ, getElements=False) + annotationGenerator = self._getAnnotation(annotation, {})() except AccessException: continue yield from annotationGenerator