Appends PostgREST v16.0's default Vary header to every non-error response.
v16.0 added Vary: Accept, Prefer, Range so caching proxies key on the three
request headers that change the representation. It is emitted from a single
funnel — App.hs' toWaiResponse, which appends varyHeader to the header
list it was handed:
Wai.responseLBS st (hdrs ++ serverTimingHeaders timing
++ warningHeaders warnMsgs
++ [varyHeader | not $ varyHeaderPresent hdrs]) bodTwo consequences are pinned by the conformance suite and reproduced here:
It is not route-specific. Reads, writes, RPC and
OPTIONSall carry it, because they all pass through that one funnel — hence aregister_before_send/2callback on the shared router pipeline rather than a header set at each rendering site.A response that already carries a
Varykeeps its own. Theresponse.headersGUC is merged intohdrsbefore thevaryHeaderPresentguard runs (Response.hsaddHeadersIfNotIncluded), soset_config('response.headers', '[{"Vary": "..."}]', true)replaces the default verbatim instead of adding a secondVary.
Error responses are the exception: they never reach toWaiResponse at all.
The outer handler maps a Left straight through Error.errorResponseFor,
whose header list is closed (Content-Type, Content-Length, Proxy-Status, plus
the error's own headers) and contains no Vary. Bier.Plugs.FallbackController
is that funnel here, and marks its responses with mark_error/1.
Note this covers only responses built by the error funnel. A 416
out-of-bounds range response is assembled by the ordinary read path with an
error envelope swapped into its body (Response.hs), so it goes through
toWaiResponse like any other read and does carry the default Vary.
Origin — a deliberate divergence
When the response echoes the request's Origin into
Access-Control-Allow-Origin, Origin is appended to the default:
Vary: Accept, Prefer, Range, Origin. A response whose headers depend on a
request header must name that header in Vary (RFC 9111 §4.1); upstream does
not, because it builds its CORS policy with corsVaryOrigin = False
(Cors.hs). See #98 and the README's "Deliberate divergences from PostgREST".
It is appended here, in the funnel, rather than set by Bier.Plugs.Cors:
a Vary written upstream of the funnel trips the "already carries a Vary"
guard and suppresses the default on exactly the requests that carry an
Origin.
Three cases stay bare, each for its own reason:
- a wildcard
Access-Control-Allow-Origin: *is not an echo — every origin gets the same header, so nothing varies by it; - an origin outside the allowlist gets no CORS header at all;
- a preflight is answered by the wai-cors middleware upstream, before
toWaiResponseever runs, so itsVaryis not this funnel's to change.
Summary
Functions
True when conn was built by the error funnel (see mark_error/1).
Mark conn as an error response, so the default Vary is not appended.
Functions
@spec error_response?(Plug.Conn.t()) :: boolean()
True when conn was built by the error funnel (see mark_error/1).
Shared with the other headers App.hs appends in toWaiResponse — an error
response bypasses that funnel, so it carries none of them.
@spec mark_error(Plug.Conn.t()) :: Plug.Conn.t()
Mark conn as an error response, so the default Vary is not appended.
Called by Bier.Plugs.FallbackController for every response it builds — the
equivalent of PostgREST's errors bypassing App.hs' toWaiResponse.