Serving a variant that is already in the store — everything a client can observe about a cache HIT.
AudioProxy.VariantStore owns where the bytes are; this module owns what
happens when they turn out to be there. The split is the same one the API
doc draws: a store is a backend detail, a HIT is a contract.
Two lines, and only two
lookup/1 is the HIT check — a AudioProxy.VariantStore.head/1 behind
the "is there a store at all" question, answering :miss for both
"unconfigured" and "not stored", because the render path does the same thing
either way. serve/3 turns an entry into a response.
serve/3 can still answer :miss: an entry can be evicted between the head
and the read, and nothing has been sent at that point, so the request falls
through to a render rather than failing.
Everything after the response head is committed, and there the only signal
left is an abnormal close — the same one, for the same reason, as a render
that fails after its 200 (§5). Two things reach it. A store read that
raises propagates and the adapter tears the connection down. A store read
that ends short would otherwise complete normally, so write/3 counts the
bytes and exits when they fall short of the Content-Length already
promised; see the comment there for why a well-formed short body is the
worse outcome of the two.
Proxy mode declares a length
A stored variant has a known size, and declaring it is the entire point of
caching one: Content-Length plus Accept-Ranges: bytes is what makes
seeking, resumption and progress reporting work, none of which a chunked
MISS can offer. Bandit streams a length-delimited body when the
content-length header is set before Plug.Conn.send_chunked/2, so the
bytes still leave as they are read — declared length and progressive
delivery are not a trade here.
Range handling is single-range only. A multipart/byteranges response would
buy nothing a media client asks for, and RFC 9110 §14.2 permits ignoring a
Range header outright, which is what a multi-range or malformed one gets.
A syntactically valid range that no byte of the variant can satisfy is a
416 — the one status this module produces that is not a HIT's body.
The redirect is not the variant
Redirect mode answers 302 with a short-lived presigned URL and
Cache-Control: no-store. The no-store is load-bearing rather than
cautious: the redirect's Location is a credential with an expiry, and a
cached 302 hands out URLs that have already expired. The immutable
cache-control belongs to the variant bytes, which the store serves under its
own headers — the ones the write-back stored, so a followed redirect
delivers the same Content-Type and Cache-Control a proxied HIT would
have sent.
Backends without :presign never take this path. AudioProxy.Config
refuses that combination at boot, so the runtime check here is a belt on top
of a brace — and a presign that fails despite it proxies instead, because
the bytes are readable and the client asked for audio, not for a URL.
What the client contract does not depend on
Not the backend, and not the serve mode. Both modes deliver the same bytes
under the same Content-Type, Cache-Control and ETag, and both are
range-capable — proxy mode because this module implements it, redirect mode
because the store does. What does change the observable response is the
cache state: the same URL is a chunked, non-seekable 200 while it is being
rendered and a length-declared, range-capable one afterwards. That is stated
in §5 as a contract clients must not assume their way around.
Summary
Functions
Reports whether the variant named by key is in the store, whole.
Serves a stored variant, per AP_SERVE_MODE.
Types
@type hit() :: {:ok, AudioProxy.VariantStore.entry()} | :miss
What lookup/1 found, if anything.
Functions
@spec lookup(AudioProxy.VariantStore.key()) :: hit()
Reports whether the variant named by key is in the store, whole.
:miss covers every reason a request must render: no store configured,
nothing stored under the key, an entry whose metadata never landed.
@spec serve( Plug.Conn.t(), AudioProxy.VariantStore.key(), AudioProxy.VariantStore.entry() ) :: {:ok, Plug.Conn.t()} | :miss
Serves a stored variant, per AP_SERVE_MODE.
:miss means the entry disappeared before a byte was sent and the caller
should render — see the moduledoc. Any returned conn is sent and halted.