Skip to content

Commit

Permalink
Merge pull request #978 from girder/etag-annotation
Browse files Browse the repository at this point in the history
Support etags on annotations to improve browser caching.
  • Loading branch information
manthey authored Oct 5, 2022
2 parents c329795 + 4a23849 commit c1b2dd5
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 6 additions & 2 deletions girder/girder_large_image/rest/tiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand Down
22 changes: 12 additions & 10 deletions girder_annotation/girder_large_image_annotation/rest/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit c1b2dd5

Please sign in to comment.