How to add HTTP caching (ETag and Cache-Control)
View SourceProblem
You want clients and CDNs to revalidate cheaply: hold a copy, send
If-None-Match, and get a bodyless 304 Not Modified when nothing
changed. You also want to set Cache-Control.
Solution
Add livery_etag to the stack. It gives a strong ETag to cacheable
GET/HEAD responses and answers 304 on a matching If-None-Match:
Stack = [
{livery_etag, #{}}
%% ... handler
].A first request returns 200 with an ETag; a later request that sends
that ETag in If-None-Match gets a 304 with no body.
Setting your own ETag and Cache-Control
The middleware computes an ETag automatically from {full, _} bodies
that don't already have one. A handler can set its own (respected on any
body type, including file/chunked):
show(Req) ->
Resp = livery_resp:json(200, render(Req)),
R1 = livery_resp:with_etag(<<"post-42-v3">>, Resp), %% -> ETag: "post-42-v3"
livery_resp:with_cache_control([public, {max_age, 300}], R1).with_cache_control/2 takes a verbatim binary or a directive list
(no_cache, no_store, public, private, immutable,
must_revalidate, proxy_revalidate, no_transform, {max_age, N},
{s_maxage, N}, {stale_while_revalidate, N}, {stale_if_error, N}).
Options
{livery_etag, #{
auto => true, %% auto-hash full bodies without an ETag (default)
weak => false, %% auto ETags are strong "..."; true emits W/"..."
statuses => [200] %% statuses eligible for ETag/304 (default [200])
}}With auto => false the middleware only acts on handler-set ETags.
Placement relative to compression
Put livery_etag OUTSIDE livery_compress (earlier in the stack list)
so the ETag covers the bytes actually sent on the wire:
Stack = [{livery_etag, #{}}, {livery_compress, #{}} | Rest].If you place it inside compression, the ETag is computed from the
uncompressed body; rely on Vary: Accept-Encoding (which
livery_compress already sets) so caches keep per-encoding variants
distinct.
Notes
If-None-Match: *matches any current representation; weak comparison (RFC 9110) is used, soW/"x"and"x"match.- The
304is bodyless and dropscontent-*headers while preservingETag,Cache-Control, andVary. - Only
GET/HEADare handled; unsafe-method preconditions (412) are out of scope.
See also
- Reference:
livery_etag,livery_resp(with_etag/2,with_cache_control/2) - Recipe: Compress responses