ImagePipe generates Cache-Control, ETag, Age, and Vary headers for
successful GET and HEAD image responses, and answers If-None-Match with
304 Not Modified. The http_cache setting chooses which headers it sends:
Plug
forward "/images", ImagePipe.Plug,
http_cache: :auto,
sources: [...]image_pipe_server
[http]
http_cache = "auto"Putting a CDN in front is covered in Serving images through a CDN, and where cache lifetimes come from in Caching and freshness. The telemetry events are listed in HTTP cache events.
Header modes
| Value | Headers |
|---|---|
validators (default) | An ETag. Sources whose files can change also get their source lifetime |
auto | Cache-Control: public, max-age=31536000, immutable and an ETag. private in some cases (see Vary) |
public | The same as auto, always public |
private | The same as auto, always private |
In every mode:
- Sources whose files can change get their source lifetime in place of the one-year lifetime.
- A response that may not be stored gets
Cache-Control: no-storeand noETag. That covers a source whose storage is denied, by its origin or by configuration (see storage permission), and a crop that fell back to a default because content detection failed. - A URL with an expiry gets a lifetime that ends with it (see expiring URLs).
Per-source modes
A source's http_cache setting replaces the global value for that source.
Its default, inherit, uses the global value. A source of per-user uploads
can be private while other sources stay public:
Plug
uploads: [
adapter: ImagePipe.Source.File,
match: [prefix: "uploads"],
options: [root: "/srv/uploads", root_id: "uploads", http_cache: :private]
]image_pipe_server
[sources.uploads]
adapter = "file"
match = { prefix = "uploads" }
root = "/srv/uploads"
root_id = "uploads"
http_cache = "private"Source lifetimes
A response from a source whose files can change gets the original's
lifetime as max-age, and its age as Age. Making a
new size or format of the original doesn't restart that lifetime.
- An origin
no-cacheaddsno-cache, ormust-revalidatewhen the source forces a lifetime. - An origin
must-revalidate,proxy-revalidate, ors-maxageaddsmust-revalidate. - A
stale-while-revalidatewindow that applies to the original is added asstale-while-revalidate. A window forced by the source drops theno-cacheandmust-revalidateabove. - A source with no lifetime from its origin and no fallback lifetime gets
max-age=0. Local files have no origin, so they getmax-age=0unless their source sets a fallback lifetime (see the Plug settings or the server's[sources.<name>]keys).
For example, an original fetched 60 seconds ago with
Cache-Control: max-age=3600 gives:
Cache-Control: public, max-age=3600
Age: 60Immutable source headers
A source marked immutable (stable set to immutable, see
immutable sources) is the
only kind that gets the one-year lifetime from the header modes
table, as long as storage is allowed. In validators mode it gets only an
ETag. Its ETag comes from the original's identifier, such as its path or
URL, instead of its content. An S3 object addressed by a version ID counts as
immutable without being marked.
Expiring URLs
A URL with an expires time never gets a cache
lifetime that outlasts it:
max-ageands-maxageare lowered to the time left.stale-while-revalidateis shortened so it also ends by then, or dropped.must-revalidateis added.
In validators mode, a response from an immutable source that carries a
generated ETag gets Cache-Control: public, max-age=<seconds left>, must-revalidate,
with private in place of public when storage_inputs names a cookie.
Vary
A URL without a format option can get AVIF or WebP chosen from the
request's Accept header. Its response then carries:
Vary: AcceptA URL with a format option doesn't vary by Accept. Neither does any URL
when the server can return neither AVIF nor WebP, because they're turned off
or the server can't encode them. The auto_avif and auto_webp settings
turn them off
(Plug,
server).
Headers named in storage_inputs are added to Vary too, in lower case,
sorted, without duplicates, and ahead of Accept. With these settings and no
format in the URL, the response carries Vary: x-tenant, Accept:
Plug
storage_inputs: [{:header, "x-tenant"}, {:cookie, "session"}]image_pipe_server
[cache]
storage_inputs = [{ header = "x-tenant" }, { cookie = "session" }]Cookies never appear in Vary, since it names headers only. So when
storage_inputs names a cookie, auto and validators send private in
place of public, on cache hits and 304 responses as well. public sends
public anyway, for a host that guarantees the responses are the same for
every user.
In a Plug app, a Vary header set by an earlier Plug is merged with
ImagePipe's. After Vary: Accept-Encoding, the response carries
Vary: Accept-Encoding, Accept. An earlier Vary: * is kept as it is (see
headers set by the host).
ETag
A generated ETag is strong and looks like "ipr1-<hash>". It is computed
from the request before the original is fetched or processed. It changes
when any of these change:
- The original's version (see originals and processed images).
- The processing options. Two spellings of the same options give the same
ETag. - The output format.
- The content detector or model, when the crop uses detection. A
304is never sent for an image made by a different detector.
The URL's cachebuster and the storage_inputs values select a different
entry in ImagePipe's own cache, but don't change the ETag. A new cachebuster
on an unchanged image stores a new copy, and clients that already have the
image still get a 304 (see cache key inputs).
A CDN keys on the raw URL. Two URLs that spell the same options differently
get the same ETag but are stored as two CDN objects.
An origin's own ETag is used only to check the original with the origin. It
never becomes a response ETag.
Conditional requests
A GET or HEAD whose If-None-Match lists the generated ETag gets
304 Not Modified without decoding, processing, or encoding the image. The
comparison is weak, so both of these match "ipr1-token":
If-None-Match: "ipr1-token"
If-None-Match: W/"ipr1-token"Whether ImagePipe contacts the source first depends on the source:
- A local immutable source answers
304without reading the file, unless the source keeps copies of its files in the originals cache. - A remote immutable source whose storage is allowed in its cache policy
answers
304without contacting the origin. - Any other source answers
304without contacting the origin while its original is within its lifetime. This needs one of ImagePipe's caches, which store the original's lifetime. Without them, the original is downloaded or read on every request, and the304is sent once its content matches. - An original past its lifetime is checked with its origin before the
304is sent (see originals and processed images). - Within a
stale-while-revalidatewindow, a processed image that is already cached gets its304at once (see stale-while-revalidate).
If-None-Match: * matches only a response served from ImagePipe's cache.
On a cache miss the request is processed and returns 200. A header that
mixes * with listed tags is treated as *.
A HEAD response carries the same ETag, Cache-Control, and Vary as the
matching GET. OPTIONS gets 204. Other methods get 405 before the URL
is parsed or the cache is read.
Headers set by the host
In a Plug app, headers set by an earlier Plug take precedence over generated ones, in every mode:
- An earlier
Cache-Controlis kept. On an expiring URL, a capped copy replaces it unless it hasno-store. ImagePipe may still add itsETag. - An earlier
ETagis kept, and ImagePipe sends no generatedETag. A request that matches the earlierETagdoesn't get a304. - A response with
Set-Cookiegets no generatedCache-ControlorETag. - An earlier
Cache-Control: no-storestops the generatedETag. - An earlier
Vary: *stops the generatedCache-ControlandETag. Plug.Conn's defaultCache-Control: max-age=0, private, must-revalidatecounts as unset. The same directives in another order, such asprivate, max-age=0, must-revalidate, count as set.
A source whose storage is denied, by its origin or by configuration, gets
Cache-Control: no-store even when an earlier Plug set Cache-Control or
Set-Cookie.
Allowing storage in the source's cache policy overrides an origin's denial
(see the Plug settings or the
server's [sources.<name>] keys).
Vary: Accept is sent whenever the format comes from Accept, even when the
generated Cache-Control and ETag are not, unless an earlier Plug set
Vary: *.
Custom validators
ImagePipe generates only ETag validators and answers only
If-None-Match. It doesn't send Last-Modified or answer
If-Modified-Since. In a Plug app, those are left to Plugs that run before
ImagePipe.