ecluse:ecluse-core
Safe HaskellNone
LanguageGHC2021

Ecluse.Core.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 serve our own strong ETag (mkStrongETag) and answer the client's conditional request against it (evaluateETag).

The own-ETag is derived from the serve's inputs, not hashed over its output: a SHA-256 over the origin bodies' digests, the per-source surviving version sets, and the assembly's identity (see packumentETag). The served document is a deterministic function of exactly those inputs, so the tag can never validate a stale body as fresh -- the direction correctness needs -- while it may occasionally change when the re-assembled bytes would not have (a spurious 200, never a wrong 304). Deriving it from inputs is what lets the serve path answer a 304 without assembling, encoding, or hashing the document at all, and stream a 200 body without materialising it for a hash pass first. 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.Core.Server.Conditional

Methods

showsPrec :: Int -> ETag -> ShowS #

show :: ETag -> String #

showList :: [ETag] -> ShowS #

Eq ETag Source # 
Instance details

Defined in Ecluse.Core.Server.Conditional

Methods

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

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

Ord ETag Source # 
Instance details

Defined in Ecluse.Core.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 #

mkStrongETag :: Digest SHA256 -> ETag Source #

Quote a SHA-256 digest as a strong ETag -- hex-encoded, in the quoted opaque-tag wire form. The digest is whatever fingerprint the serving layer stands behind; for packuments that is the input fingerprint of packumentETag.

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.Core.Server.Conditional

Eq Conditional Source # 
Instance details

Defined in Ecluse.Core.Server.Conditional

evaluateETag :: RequestHeaders -> ETag -> Conditional Source #

Evaluate a conditional request against our own ETag for a transformed body.

The given tag is 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 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.