ETag/If-None-Match
we explored the usefulness of the Last-Modified Response Header and If-Modified-Since Request Header. They work really well when dealing with an endpoint returning a file. What about data retrieved from a database or assembled from different sources?
In the previous post , we explored the usefulness of the Last-Modified
Response Header and If-Modified-Since
Request Header. They work really well when dealing with an endpoint returning a file.
What about data retrieved from a database or assembled from different sources?
Request | Response | Value example |
---|---|---|
Last-Modified | If-Modified-Since | Thu, 15 Nov 2023 19:18:46 GMT |
ETag | If-None-Match | 75e7b6f64078bb53b7aaab5c457de56f |
Also here, we have a tuple of headers. One must be provided by the requester (ETag
), while the other is returned by the sender (If-None-Match
). The value is a hash generated on the content of the response.
If you want to go directly to using headers, go to the endpoint. Otherwise, observe (but don't spend too much time on) the implementation.
Preparation
For simplicity, we use an in-memory DB. It is exposed via the endpoint /db
. It contains a list of posts
. Each post contains a title
and a tag
. Posts can be added via POST
, and modified via PATCH
.
Retrieval is via a GET
function, which optionally filters by tag
.
Endpoint
By registering the db, we will be able to modify the content of the responses in real-time, appreciating the usefulness of ETag.
Also, let's register and create the /only-etag
endpoint.
The onlyETag
endpoint accepts an optional query parameter tag
. If present, it is used to filter the retrieved posts.
Thus, the template is loaded in memory.
When submitted, the form uses as action
the current route (/only-etag
) appending as query parameter the name
attribute. For example, typing code
in the input and submitting the form would result in GET /only-etag?name=code
), No JavaScript required!
And the posts are injected into it.
As you notice, before dispatching the response, the ETag is generated and included under the ETag
Response header.
Changing the content of the resource changes the Entity Tag.
Performing the request from the browser you can inspect the Response Headers via the Network tab of the Developer Tools.
If you refresh the page, you'll notice the browser adding the If-None-Match
header to the request. The value corresponds of course to the one it received before.
As seen in the previous posts per Last-Modified
and If-Modified-Since
, let's instruct the endpoint to deal with If-None-Match
.
Indeed, subsequent requests on the same resource return 304 Not Modified
, instructing the browser to use previously stored resources. Let's request:
/only-etag
three times in a row;/only-etag?tag=code
twice;/only-etag?tag=animals
twice;/only-etag
, without tag, once again;
The presence of the query parameter determines a change in response, thus in ETag.
Notice the last one. It does not matter that there have been other requests in the meantime; the browser keeps a map of requests (including the query parameters) and ETags.
Detect entity change
To further underscore the significance of this feature, let's add a new post to the DB from another process.
And request again /only-etag?tag=code
. After the db has been updated, the same request generated a different ETag. Thus, the server sent the client a new version of the resource, with a newly generated ETag. Subsequent requests will fall back to the expected behavior.
The same happens if we modify an element of the response.
While ETag is a more versatile solution, applicable regardless of the data type since it is content-based, it should be considered that the server must still retrieve and assemble the response, then pass it into the hashing function and compare it with the received value.
Thanks to another header, Cache-Control
, it is possible to optimize the number of requests the server has to process.
On This Page