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.