ETag and Cache-Control
Add response validators and cache directives.
ETag and Cache-Control
Ply provides two small middleware helpers for HTTP caching.
app.Use(
middleware.Compress(0),
middleware.ETag(),
)ETag computes a strong validator from the response body and answers
conditional requests. CacheControl sets a fixed Cache-Control header.
ETag
app.Get("/article", func(c *core.Context) {
c.Text(200, "article body")
})The response includes:
ETag: "c5f4f3c9a8e1d2b7f6a5c4e3d2b1a0f9"On the next request the client sends:
If-None-Match: "c5f4f3c9a8e1d2b7f6a5c4e3d2b1a0f9"Ply returns 304 Not Modified with an empty body.
Behavior
- Only
GETandHEADresponses are processed. - Only
200responses with a non-empty body receive an ETag. - Streaming responses are skipped.
- An existing
ETagset by the handler is preserved. - Weak validators (
W/"...") match the strong tag forIf-None-Match. - The tag is derived from the first 16 bytes of SHA-256, giving 32 hex characters.
CacheControl
app.Get("/assets/config.json", middleware.CacheControl("public, max-age=300"), func(c *core.Context) {
c.Ctx.SendFile("public/config.json")
})Use middleware for a shared policy:
app.Use(middleware.CacheControl("no-store"))| Value | Use |
|---|---|
no-store | Sensitive responses that must not be cached |
no-cache | Revalidate before using a cached copy |
public, max-age=60 | Shared cache for one minute |
private, max-age=300 | Browser cache only |
Ordering with compression
Place Compress before ETag in the middleware list:
app.Use(
middleware.Compress(0),
middleware.ETag(),
)Compress wraps ETag, so the tag is computed from the uncompressed body and
stays stable when the client changes Accept-Encoding.
If ETag wraps Compress, the tag is computed from the compressed bytes. That
still works, but the validator changes with the encoding.
Caching correctness
ETag handles validators, not cache duration. Set Cache-Control separately.
Do not cache responses that depend on a user:
app.Get("/me", func(c *core.Context) {
c.Ctx.Response.Header.Set("Cache-Control", "private, no-store")
c.JSON(200, currentUser(c))
})For shared caches, include Vary when the response changes by request header,
and avoid caching authenticated responses unless the cache is configured to
respect Authorization.

Ply