ecluse
Safe HaskellNone
LanguageGHC2021

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 upstream 304 is 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 ETag over 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

Our own ETag (transformed bodies)

data ETag Source #

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.

Instances

Instances details
Show ETag Source # 
Instance details

Defined in Ecluse.Server.Conditional

Methods

showsPrec :: Int -> ETag -> ShowS #

show :: ETag -> String #

showList :: [ETag] -> ShowS #

Eq ETag Source # 
Instance details

Defined in Ecluse.Server.Conditional

Methods

(==) :: ETag -> ETag -> Bool #

(/=) :: ETag -> ETag -> Bool #

Ord ETag Source # 
Instance details

Defined in Ecluse.Server.Conditional

Methods

compare :: ETag -> ETag -> Ordering #

(<) :: ETag -> ETag -> Bool #

(<=) :: ETag -> ETag -> Bool #

(>) :: ETag -> ETag -> Bool #

(>=) :: ETag -> ETag -> Bool #

max :: ETag -> ETag -> ETag #

min :: ETag -> ETag -> ETag #

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 304 with this ETag, no body.

Modified ETag

The served body differs (or no validator was sent) — serve 200 with this ETag header.

Instances

Instances details
Show Conditional Source # 
Instance details

Defined in Ecluse.Server.Conditional

Eq Conditional Source # 
Instance details

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.