| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.Server.Conditional
Description
Conditional-GET / ETag handling, split by how the served body relates to upstream's.
The proxy serves two kinds of body, and they validate differently (see
docs/architecture/web-layer.md → "Middleware and helper libraries"):
- Pass-through bodies — artifacts, and unfiltered private-upstream metadata —
are byte-identical to upstream's, so upstream's own validator is authoritative.
The client's validators are relayed upstream (
forwardValidators) and an upstream304is passed straight back (isNotModified). Relaying is correct precisely because we do not change the bytes. - Transformed bodies — every packument, which is merged across upstreams and
filtered by the rules — differ from any single upstream's body, so an upstream
validator would validate the wrong bytes. We instead compute our own strong
ETagover what we serve (ownETag) and answer the client's conditional request against that (evaluateOwnETag).
The own-ETag is a SHA-256 over the exact served bytes, so it changes iff the
served document changes — a filtered version dropping in or out, a latest
repoint, an integrity divergence — and never collides a stale body onto a fresh
one. The functions here are pure; turning a Conditional or relayed status into a
WAI response is the serving layer's job.
Synopsis
- data ETag
- ownETag :: LByteString -> ETag
- renderETag :: ETag -> Text
- etagHeader :: ETag -> Header
- data Conditional
- evaluateOwnETag :: RequestHeaders -> LByteString -> Conditional
- forwardValidators :: RequestHeaders -> RequestHeaders
- isNotModified :: Status -> Bool
Our own ETag (transformed bodies)
A strong entity tag for a body we serve: the quoted opaque-tag form
("…"), as it appears in the ETag header. A 'newtype' so the quoted wire form
is not confused with the bare digest or any other Text.
ownETag :: LByteString -> ETag Source #
Compute our own strong ETag over the served bytes — a SHA-256 digest,
hex-encoded and quoted. It tracks exactly what we serve, so a transformed
(filtered, merged) packument gets a validator that changes when, and only when,
the served document does. Computing it over upstream's body instead would
validate the wrong bytes.
renderETag :: ETag -> Text Source #
The ETags wire form, the quoted opaque tag as it goes into the header.
etagHeader :: ETag -> Header Source #
The ETag response header carrying this validator.
data Conditional Source #
The conditional outcome for a transformed body: whether the client's validator already matches what we would serve.
Constructors
| NotModified ETag | The served body is unchanged from the client's validator — answer |
| Modified ETag | The served body differs (or no validator was sent) — serve |
Instances
| Show Conditional Source # | |
Defined in Ecluse.Server.Conditional Methods showsPrec :: Int -> Conditional -> ShowS # show :: Conditional -> String # showList :: [Conditional] -> ShowS # | |
| Eq Conditional Source # | |
Defined in Ecluse.Server.Conditional | |
evaluateOwnETag :: RequestHeaders -> LByteString -> Conditional Source #
Evaluate a conditional request against our own ETag for a transformed body.
The body's ownETag is computed, then matched against the request's
If-None-Match: a * wildcard, or any tag in the (comma-separated) list whose
opaque value equals ours, is a match → NotModified. The match is weak (RFC
7232): a W/ prefix on either side is ignored, so a client echoing our tag with a
weakness marker still matches. Anything else — a stale tag, or no validator —
is Modified.
If-Modified-Since is deliberately not consulted for transformed bodies: a merged
packument has no single upstream Last-Modified to compare to, and the strong
content ETag is the precise validator.
Relaying validators (pass-through bodies)
forwardValidators :: RequestHeaders -> RequestHeaders Source #
The client's conditional validators to relay upstream for a pass-through
body. Only the request-side conditional headers (If-None-Match,
If-Modified-Since) are forwarded; everything else is dropped, since this is the
exact set that lets upstream answer 304 for a body we serve unchanged.
isNotModified :: Status -> Bool Source #
Whether an upstream response is a 304 Not Modified to pass straight back to
the client unchanged. Used on the pass-through path, where upstream's own
validator decided the conditional request.