HTTP Caching

HTTP caching is a mechanism that shines in RESTful APIs. A GET or HEAD response on a detail resource can be augmented with the ETag header, which essentially is a “version” of a resource. Every modification of the resource leads to a new version.

This can be leveraged in combination with the If-None-Match request header, kwown as conditional requests.

The client includes the ETag value(s) in this header, and if the current version of the resource has the same ETag value, then the server replies with an HTTP 304 response, indicating that the content has not changed.

This allows consumers to send less data over the wire without having to perform extra requests if they keep resources in their own cache.

See the ETag on MDN documentation for the full specification.

Implementing conditional requests

Two actions are needed to implement this in API implementations:

Add the model mixin to save the ETag value

This enables us to perform fast lookups and avoid calculating the ETag value over and over again. It happens automatically on save of a model instance.

1from vng_api_common.caching import ETagMixin
2
3
4class MyModel(ETagMixin, models.Model):
5    pass

Note

If you’re doing queryset.update or bulk_create operations, you need to take care of setting/calculating the value yourself.

Decorate the viewset

Decorating the viewset retrieve ensures that the ETag header is set, the conditional request handling (HTTP 200 vs. HTTP 304) is performed and the extra methods/headers show up in the generated API schema.

 1from rest_framework import viewsets
 2from vng_api_common.caching import conditional_retrieve
 3
 4from .models import MyModel
 5from .serializers import MyModelSerializer
 6
 7
 8@conditional_retrieve()
 9class MyModelViewSet(viewsets.ReadOnlyViewSet):
10    queryset = MyModel.objects.all()
11    serializer_class = MyModelSerializer

Public API

Facilitate HTTP caching mechanisms.

This package contains the tooling required to (efficiently) apply and use ETag HTTP headers.

On a request level, the API will return HTTP 304 statuses if the client has an up to date version of the resource. It will reply with a HTTP 200 otherwise, including the ETag header.

This package provides a model mixin to save the ETag header value to the db, and a decorator to enable conditional requests on viewsets. The rest are implementation details.

class vng_api_common.caching.ETagMixin(*args, **kwargs)

Automatically calculate the (new) ETag value on save.

Note that the signal receivers in vng_api_common.caching.signals are responsible for (cached) _etag value invalidation.

calculate_etag_value() str

Calculate and save the ETag value.

vng_api_common.caching.calculate_etag(instance: Model) str

Calculate the MD5 hash of a resource representation in the API.

The serializer for the model class is retrieved, and then used to construct the representation of the instance. Then, the representation is rendered to camelCase JSON, after which the MD5 hash is calculated of this result.

vng_api_common.caching.conditional_retrieve(action='retrieve', etag_field='_etag', extra_depends_on: Optional[Set[str]] = None)

Decorate a viewset to apply conditional GET requests.

The decorator patches the handler to calculate and emit the required ETag-related headers. Additionally, it sets up the dependency tree for the exposed resource so that the ETag value can be recalculated when the resource or relevant related resources are modified, resulting in an updated ETag value. This is introspected through the specified viewset serializer class.

Parameters:
  • action – The viewset action to decorate

  • etag_field – The model field containing the (cached) ETag value

  • extra_depends_on – A set of additional field names the ETag value calculation depends on. Normally, this is inferred from the serializer, but in some cases ( .e.g. SerializerMethodField) this cannot be automatically detected. These fields will be added to the automatically introspected serializer relations.