arizona_req behaviour (arizona v0.4.0)
View SourceHTTP request abstraction with adapter pattern for different web servers.
Provides a unified interface for HTTP requests behind a transport behaviour each server adapter implements. Features lazy loading of request data with caching to avoid re-parsing across accesses.
Adapter pattern
A web-server adapter implements the mandatory parsing callbacks:
parse_bindings/1-- extract route-pattern path bindingsparse_params/1-- parse the query string as a proplistparse_cookies/1-- parse the Cookie header as a proplistparse_headers/1-- headers as a mapread_body/1-- consume the request body:{ok, Body, Raw1}, or{error, body_error()}when the read failsscheme/1-- the connection scheme (http|https)
Plus one optional callback for live-capable transports:
resolve_route/3-- resolve a SPA-navigate target. Required for WebSocket sessions, omitted for HTTP-only adapters.
The adapter module is stored inside the request record itself, so every
accessor dispatches to the correct backend, and arizona_socket
recovers the adapter via arizona_req:adapter/1 for navigate.
Lazy loading
Fields other than method and path are populated on first access and
cached. Accessors return {Value, Request1} -- the updated request
carries the cached value. Callers thread the returned request forward.
The one exception is body/1, whose read can fail: it returns
{ok, Body, Request1} or {error, body_error()}.
Method = arizona_req:method(Req). %% eager, no thread
{Bs, Req1} = arizona_req:bindings(Req).
{Params, Req2} = arizona_req:params(Req1).
{ok, Body, Req3} = arizona_req:body(Req2).
Summary
Functions
Returns the adapter module that backs the request.
Returns route-pattern path bindings. Lazy-loaded via the adapter on first access, cached in the returned request for subsequent calls.
Consumes the request body. Lazy-loaded; the underlying raw request is updated because reading a body is mutating in most transports.
Clears the entire session (logout). On the response a clearing Set-Cookie is
sent regardless of the incoming session, and in store mode the server-side entry
is dropped at flush -- logout revokes, so a captured signed-id cookie stops
resolving immediately, whether or not anything read the session earlier this
request (the incoming cookie's id is resolved here). In cookie mode it does not
require secret_key -- clearing encrypts nothing, so logout always works; store
mode reads the signed id with the secret_key store mode requires anyway.
The scheme the browser used: scheme/1, upgraded to https when a proxy in
front reports the original request was (X-Forwarded-Proto, its first,
client-facing entry). Upgrade-only, so a forged header can tighten what depends
on it (the Origin check rejecting the forger's own request) but never loosen it,
which is why it needs no trusted-proxy configuration. Threads the request, whose
headers it may have parsed.
Commits a written session to the server-side store (store mode): a dirty
non-empty session is put under its id -- and, on a rotation (clear_session/1
then put_session/3, the login pattern), the pre-rotation entry is dropped so a
fixated id cannot be reused -- while a cleared one (logout) is deleted. A no-op
in cookie mode or when the session is untouched.
Returns parsed cookies as a proplist. Lazy-loaded and cached.
Removes Key from the session and marks it written. On the response the session
serializes to the re-encrypted (still non-empty) cookie, or a clearing cookie if
this removed the last key.
Returns the incoming flash for this request (the message a put_flash/3 set on the
prior request before a redirect), or #{} when there is none.
Returns the outgoing flash this request has staged via put_flash/3 (destined for
the next request), or #{} when none was set.
Ends the request: commits a written session (commit_session/1) and returns
the header lines to add to the response, in the order they must be sent:
one set-cookie line per resp_cookies/1 entry (serialized by
arizona_cookie:serialize/3), then the stashed headers (put_resp_header/3),
each oldest first. Values are binaries.
Looks up Key in the effective session, returning {ok, Value} or error. Key
may be an atom or binary (normalized). Reads the post-write state when the session
was written earlier this request.
Like get_session/2 but returns Default instead of error when absent.
The response to a middleware {halt, Request}: its status, and its header
lines in send order -- flush/1's (so it also commits the session), then a
redirect's location. The body is empty.
Returns the stashed redirect intent, or undefined when no
redirect/2,3 was called on this request. Transports use this to
translate a middleware {halt, Req} return into the appropriate
client-visible response.
Returns request headers as a map. Lazy-loaded and cached.
Classifies a redirect Location the way a browser will read it, for a transport
that must deliver it as a client navigation rather than an HTTP 3xx.
Returns the HTTP method (e.g. ~"GET", ~"POST").
Creates a request backed by Adapter wrapping Raw.
Returns parsed query-string params as a proplist. Lazy-loaded and cached.
Returns the request path (no query string).
The connection's peer address and port, or undefined when the adapter does not
report it. The immediate TCP peer: behind a same-host proxy it is the proxy.
Stashes a flash message to carry across one redirect (the Post/Redirect/Get
pattern). On the response it serializes to a signed, short-lived Set-Cookie;
the next request reads it once via flash/1 (or the auto-injected flash
binding) and it is then cleared. Key may be an atom or binary (normalized to a
binary); repeated calls accumulate, last write wins per key.
Stashes a response cookie on Request, serialized by the transport when the
response is built. Lets a middleware set a cookie alongside a redirect/2,3
or a rendered page. Opts follows resp_cookie_opts().
Stashes a response header on Request. Transports flush stashed headers
onto the outgoing response (redirect, rendered page, or halt). Repeated
calls accumulate (newest first).
Stashes the HTTP status for a rendered page. Lets a view or middleware return
a non-200 status (e.g. 401 from an auth gate, 404 from a not-found page)
while still rendering a body. Ignored for redirects and halts, which carry
their own status.
Writes Key => Value into the session. On the response the session serializes to a
freshly encrypted Set-Cookie (durable -- it persists until overwritten or
cleared). Key may be an atom or binary (normalized to a binary); repeated calls
accumulate, last write wins per key.
Returns the raw, transport-native request value the adapter wraps.
Reads, authenticates, and decodes the incoming session cookie into the request.
Returns the session map (#{} when absent, tampered, malformed, or expired) and the
updated request. Idempotent and non-consuming: unlike consume_flash/1 it does not
mark the session for clearing -- a durable session survives a read untouched.
Stashes a 302 redirect intent in Request so downstream transports
translate it uniformly.
Like redirect/2 but lets callers pick the 3xx status code.
Returns the adapter-supplied request ID (e.g. roadrunner's 16-char
hex correlation token), or undefined if the adapter doesn't
populate one.
Returns the stashed response cookies (newest first), or [], plus the flash and
session cookies when warranted: a freshly put_flash/3-set flash serializes to a
signed Set-Cookie (a consumed incoming flash with no new one serializes to a
clearing cookie); a written session (put_session/3/delete_session/2) serializes
to an encrypted Set-Cookie (in store mode, the signed session id), a cleared one
(clear_session/1) to a clearing cookie, and an untouched session to nothing.
Transports flush this list verbatim.
Returns the stashed response headers (newest first), or [].
Returns the stashed response status, or undefined.
Returns the scheme of the connection the request arrived on (http | https),
straight from the adapter -- eager, nothing to thread.
Returns the effective session for this request: the pending written state
(session_out) when it has been written this request, otherwise the incoming
session (session_in, #{} if not yet read). A view or controller sees the session
including its own writes.
Returns {error, Reason} when reading the server-side session failed this request, or
undefined when it did not.
Returns the opaque session id for this request in store mode (session_store
configured), or undefined in cookie mode or when there is no session yet. Apps can
record it to revoke the session later via the store's delete/1.
Replaces the raw value inside Request and clears all lazy caches.
Returns the User-Agent request header (or <<>> if absent).
Types
-nominal adapter() :: module().
-nominal body() :: binary().
-nominal body_error() :: too_large | trailers_too_large | timeout | invalid.
-nominal method() :: binary().
-nominal path() :: binary().
-nominal qs() :: binary().
-nominal raw() :: dynamic().
-nominal redirect_status() :: 300..399.
-opaque request()
-nominal resp_status() :: 100..599.
-nominal scheme() :: http | https.
Callbacks
-callback peer(raw()) -> {inet:ip_address(), inet:port_number()} | undefined.
-callback read_body(raw()) -> {ok, body(), raw()} | {error, body_error()}.
Functions
Returns the adapter module that backs the request.
Returns route-pattern path bindings. Lazy-loaded via the adapter on first access, cached in the returned request for subsequent calls.
-spec body(request()) -> {ok, body(), request()} | {error, body_error()}.
Consumes the request body. Lazy-loaded; the underlying raw request is updated because reading a body is mutating in most transports.
Returns {error, Reason} when the transport could not deliver the body (see
body_error/0) -- a body over the server's cap read on demand, a stalled or
vanished client. Nothing is cached then, so the caller answers from the
request it already holds (arizona_middleware:extract/1's body key halts
with 413, 431, 408 or 400).
-spec clear_session(Request) -> Request when Request :: request().
Clears the entire session (logout). On the response a clearing Set-Cookie is
sent regardless of the incoming session, and in store mode the server-side entry
is dropped at flush -- logout revokes, so a captured signed-id cookie stops
resolving immediately, whether or not anything read the session earlier this
request (the incoming cookie's id is resolved here). In cookie mode it does not
require secret_key -- clearing encrypts nothing, so logout always works; store
mode reads the signed id with the secret_key store mode requires anyway.
The scheme the browser used: scheme/1, upgraded to https when a proxy in
front reports the original request was (X-Forwarded-Proto, its first,
client-facing entry). Upgrade-only, so a forged header can tighten what depends
on it (the Origin check rejecting the forger's own request) but never loosen it,
which is why it needs no trusted-proxy configuration. Threads the request, whose
headers it may have parsed.
-spec commit_session(Request) -> ok when Request :: request().
Commits a written session to the server-side store (store mode): a dirty
non-empty session is put under its id -- and, on a rotation (clear_session/1
then put_session/3, the login pattern), the pre-rotation entry is dropped so a
fixated id cannot be reused -- while a cleared one (logout) is deleted. A no-op
in cookie mode or when the session is untouched.
Called by the transports at response flush, beside serializing resp_cookies/1
(a pure getter that performs no store writes). Flushing is the commit point, so
the cookie and the store entry always agree: a response that is never built
commits nothing. A {live, ...} render crash is not such a case -- it is caught and
flushed as a 500 error page, so a session written before the crash does commit.
Idempotent -- committing the same request again repeats the same put/delete.
Returns parsed cookies as a proplist. Lazy-loaded and cached.
Removes Key from the session and marks it written. On the response the session
serializes to the re-encrypted (still non-empty) cookie, or a clearing cookie if
this removed the last key.
Returns the incoming flash for this request (the message a put_flash/3 set on the
prior request before a redirect), or #{} when there is none.
The app-facing flash reader. The framework materializes the flash before your view
runs -- decoded from the signed cookie on a fresh request, or carried in-process
across a WebSocket SPA navigate -- so a view reads it here or through the
auto-injected flash binding. Pair it with put_flash/3 (the writer).
Returns the outgoing flash this request has staged via put_flash/3 (destined for
the next request), or #{} when none was set.
The companion to flash/1: where flash/1 reads the incoming flash (what this
request received), flash_out/1 reads the outgoing one (what it has queued to
send). The framework carries this map onward -- a signed cookie for a full-page
redirect, or in-process across a WebSocket SPA navigate -- so it is primarily an
introspection/testing helper: a test can assert what a middleware or handler
flashed before a redirect without decoding the cookie.
Ends the request: commits a written session (commit_session/1) and returns
the header lines to add to the response, in the order they must be sent:
one set-cookie line per resp_cookies/1 entry (serialized by
arizona_cookie:serialize/3), then the stashed headers (put_resp_header/3),
each oldest first. Values are binaries.
The order is part of the contract: the stash is newest first, and a browser
keeps the last Set-Cookie for a name, so a cookie written twice must
reach the wire in the order it was written. Emit the lines as listed.
A server adapter calls this, or halt_reply/1 on a middleware halt, exactly
once for every response it builds from an Arizona request, whatever the
response's shape: a page, a controller reply, a stream, a WebSocket upgrade
(whose 101 then carries the cookies). Skipping it drops the stashed cookies
and, in store mode, the session write, with nothing reporting it.
-spec get_session(Request, Key) -> {ok, term()} | error when Request :: request(), Key :: atom() | binary().
Looks up Key in the effective session, returning {ok, Value} or error. Key
may be an atom or binary (normalized). Reads the post-write state when the session
was written earlier this request.
-spec get_session(Request, Key, Default) -> term() when Request :: request(), Key :: atom() | binary(), Default :: term().
Like get_session/2 but returns Default instead of error when absent.
-spec halt_reply(Request) -> {resp_status(), [{binary(), binary()}]} when Request :: request().
The response to a middleware {halt, Request}: its status, and its header
lines in send order -- flush/1's (so it also commits the session), then a
redirect's location. The body is empty.
A halting middleware says what the client should see by stashing a redirect
(redirect/2,3: the status and a location header) or a status
(put_resp_status/2). A halt carrying neither is a refusal with no reason
disclosed, which is exactly 403 Forbidden (RFC 9110 §15.5.4): 204 would
report a denial as success (a fetch sees ok and resets the form), and 400
would blame request syntax the framework knows nothing about.
-spec halted_redirect(Request) -> {redirect_status(), binary()} | undefined when Request :: request().
Returns the stashed redirect intent, or undefined when no
redirect/2,3 was called on this request. Transports use this to
translate a middleware {halt, Req} return into the appropriate
client-visible response.
Returns request headers as a map. Lazy-loaded and cached.
A field sent on several lines arrives as one entry holding every value in the
order received, comma-separated (RFC 9110 §5.3) -- cookie uses its own ;
separator. Names are lowercase.
-spec location_kind(Location) -> local | external | unsafe when Location :: binary().
Classifies a redirect Location the way a browser will read it, for a transport
that must deliver it as a client navigation rather than an HTTP 3xx.
local-- a path on this origin (a single leading/), the only form a SPA navigation can follow.external-- a page load is needed: anhttp/httpsURL, a scheme-relative one (//host, and/\host, which browsers read the same way), or a relative path, which a browser resolves against the current URL, exactly as it would an HTTPLocation. It may still be same-origin, but the server cannot tell -- behind a proxy it does not know its public origin -- and a page load is correct for any URL.unsafe-- any other scheme (javascript:,data:, ...). Loading it would run it in the page, so no transport may hand it to the client.
The input is normalized as a browser's URL parser does first: leading C0
controls and spaces are trimmed and every tab, LF and CR is removed, so
/<TAB>/host is //host and java<TAB>script: is javascript:.
Returns the HTTP method (e.g. ~"GET", ~"POST").
-spec new(Adapter, Raw, Opts) -> request() when Adapter :: adapter(), Raw :: raw(), Opts :: #{method := method(), path := path(), request_id => binary(), bindings => bindings(), params => params(), cookies => cookies(), headers => headers(), body => body()}.
Creates a request backed by Adapter wrapping Raw.
Opts must contain method and path; any of
bindings | params | cookies | headers | body may be pre-populated to
skip their adapter callbacks on first access.
Returns parsed query-string params as a proplist. Lazy-loaded and cached.
Preserves repeat keys (?tag=a&tag=b returns two entries).
Returns the request path (no query string).
-spec peer(request()) -> {inet:ip_address(), inet:port_number()} | undefined.
The connection's peer address and port, or undefined when the adapter does not
report it. The immediate TCP peer: behind a same-host proxy it is the proxy.
-spec put_flash(Request, Key, Value) -> Request when Request :: request(), Key :: atom() | binary(), Value :: term().
Stashes a flash message to carry across one redirect (the Post/Redirect/Get
pattern). On the response it serializes to a signed, short-lived Set-Cookie;
the next request reads it once via flash/1 (or the auto-injected flash
binding) and it is then cleared. Key may be an atom or binary (normalized to a
binary); repeated calls accumulate, last write wins per key.
Requires secret_key in the arizona application env (used to sign the cookie);
errors if it is unset.
-spec put_resp_cookie(Request, Name, Value, Opts) -> Request when Request :: request(), Name :: binary(), Value :: binary(), Opts :: resp_cookie_opts().
Stashes a response cookie on Request, serialized by the transport when the
response is built. Lets a middleware set a cookie alongside a redirect/2,3
or a rendered page. Opts follows resp_cookie_opts().
-spec put_resp_header(Request, Name, Value) -> Request when Request :: request(), Name :: binary(), Value :: iodata().
Stashes a response header on Request. Transports flush stashed headers
onto the outgoing response (redirect, rendered page, or halt). Repeated
calls accumulate (newest first).
-spec put_resp_status(Request, Status) -> Request when Request :: request(), Status :: resp_status().
Stashes the HTTP status for a rendered page. Lets a view or middleware return
a non-200 status (e.g. 401 from an auth gate, 404 from a not-found page)
while still rendering a body. Ignored for redirects and halts, which carry
their own status.
-spec put_session(Request, Key, Value) -> Request when Request :: request(), Key :: atom() | binary(), Value :: term().
Writes Key => Value into the session. On the response the session serializes to a
freshly encrypted Set-Cookie (durable -- it persists until overwritten or
cleared). Key may be an atom or binary (normalized to a binary); repeated calls
accumulate, last write wins per key.
The first write seeds the pending session from the incoming session (reading the
cookie if not already read), so a write merges onto existing state even without the
fetch_session middleware. Requires secret_key in the arizona application env
(used to encrypt the cookie); errors if it is unset.
Returns the raw, transport-native request value the adapter wraps.
Reads, authenticates, and decodes the incoming session cookie into the request.
Returns the session map (#{} when absent, tampered, malformed, or expired) and the
updated request. Idempotent and non-consuming: unlike consume_flash/1 it does not
mark the session for clearing -- a durable session survives a read untouched.
Run by the fetch_session middleware (opt-in); apps normally use session/1,
get_session/2,3, or the session binding rather than calling this directly.
Stashes a 302 redirect intent in Request so downstream transports
translate it uniformly.
Middleware authors should return {halt, arizona_req:redirect(Req, Location)} instead of writing a transport-specific response
directly. HTTP transports read the intent and emit a proper redirect
reply; WS navigate transports emit an arizona_js:navigate/1
client effect so the browser pushes the new URL. Only a path on this origin
(see location_kind/1) can be followed that way: for any other Location --
an identity provider, another subdomain -- the WS transport has the browser load
the gated path over HTTP instead, where this redirect is a real 3xx.
-spec redirect(Request, Status, Location) -> Request when Request :: request(), Status :: redirect_status(), Location :: binary().
Like redirect/2 but lets callers pick the 3xx status code.
Returns the adapter-supplied request ID (e.g. roadrunner's 16-char
hex correlation token), or undefined if the adapter doesn't
populate one.
Useful for live-view handlers and crash-report glue that need to correlate Arizona-side events with the adapter's access logs.
-spec resp_cookies(Request) -> [{binary(), binary(), resp_cookie_opts()}] when Request :: request().
Returns the stashed response cookies (newest first), or [], plus the flash and
session cookies when warranted: a freshly put_flash/3-set flash serializes to a
signed Set-Cookie (a consumed incoming flash with no new one serializes to a
clearing cookie); a written session (put_session/3/delete_session/2) serializes
to an encrypted Set-Cookie (in store mode, the signed session id), a cleared one
(clear_session/1) to a clearing cookie, and an untouched session to nothing.
Transports flush this list verbatim.
A pure getter: it builds the cookie list and nothing else. In store mode the
matching store write/delete is commit_session/1, which the transports call at
response flush.
Returns the stashed response headers (newest first), or [].
-spec resp_status(Request) -> resp_status() | undefined when Request :: request().
Returns the stashed response status, or undefined.
Returns the scheme of the connection the request arrived on (http | https),
straight from the adapter -- eager, nothing to thread.
This is the transport's own view, so a deployment behind a TLS-terminating proxy
reports http even though the browser spoke HTTPS. Callers that need the
client-facing scheme (arizona_middleware:check_origin/2) fold in the proxy's
X-Forwarded-Proto header on top of this.
Returns the effective session for this request: the pending written state
(session_out) when it has been written this request, otherwise the incoming
session (session_in, #{} if not yet read). A view or controller sees the session
including its own writes.
Returns {error, Reason} when reading the server-side session failed this request, or
undefined when it did not.
Set in store mode when the store's get/1 answers {error, Reason} (the backing store
was unreachable) -- not for a genuinely absent, expired, or revoked session, nor a
tampered/forged cookie, all of which read as a signed-out request with no error. Lets an
app at the request boundary (a controller, or a middleware after fetch_session) log the
outage or serve a degraded response; the auth decision is unaffected, since a failed read
still yields an empty session. The {error, _} wrapper keeps any Reason (including
undefined) distinct from the no-error sentinel.
Returns the opaque session id for this request in store mode (session_store
configured), or undefined in cookie mode or when there is no session yet. Apps can
record it to revoke the session later via the store's delete/1.
Replaces the raw value inside Request and clears all lazy caches.
Useful after a transport-specific mutation (e.g. writing a reply on halt) that returns an updated raw. Cached fields are dropped because they may no longer reflect the new raw.
Returns the User-Agent request header (or <<>> if absent).
A view that serves both browsers (HTML) and native apps (a native JSON tree)
reads this via an arizona_middleware:extract([user_agent]) middleware and
branches in render/1. Match it directly (you know your own native client's
UA), or classify it with the pure helpers in arizona_user_agent (browser/1,
os/1, mobile/1). The framework decides and injects nothing -- you own the
binding and the branch. For an explicit signal instead, send a query param and
extract it with arizona_middleware:extract([params]).