arizona_req behaviour (arizona v0.4.0)

View Source

HTTP 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 bindings
  • parse_params/1 -- parse the query string as a proplist
  • parse_cookies/1 -- parse the Cookie header as a proplist
  • parse_headers/1 -- headers as a map
  • read_body/1 -- consume the request body: {ok, Body, Raw1}, or {error, body_error()} when the read fails
  • scheme/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

adapter()

-nominal adapter() :: module().

bindings()

-nominal bindings() :: #{binary() => dynamic()}.

body()

-nominal body() :: binary().

body_error()

-nominal body_error() :: too_large | trailers_too_large | timeout | invalid.

carried_cookies()

-nominal carried_cookies() :: [{binary(), binary() | clear}].

cookies()

-nominal cookies() :: [{binary(), binary()}].

flash()

-nominal flash() :: #{binary() => term()}.

headers()

-nominal headers() :: #{binary() => iodata()}.

method()

-nominal method() :: binary().

params()

-nominal params() :: [{binary(), binary() | true}].

path()

-nominal path() :: binary().

qs()

-nominal qs() :: binary().

raw()

-nominal raw() :: dynamic().

redirect_status()

-nominal redirect_status() :: 300..399.

request()

-opaque request()

resp_status()

-nominal resp_status() :: 100..599.

scheme()

-nominal scheme() :: http | https.

session()

-nominal session() :: #{binary() => term()}.

Callbacks

parse_bindings/1

-callback parse_bindings(raw()) -> bindings().

parse_cookies/1

-callback parse_cookies(raw()) -> cookies().

parse_headers/1

-callback parse_headers(raw()) -> headers().

parse_params/1

-callback parse_params(raw()) -> params().

peer/1

(optional)
-callback peer(raw()) -> {inet:ip_address(), inet:port_number()} | undefined.

read_body/1

-callback read_body(raw()) -> {ok, body(), raw()} | {error, body_error()}.

resolve_route/3

(optional)
-callback resolve_route(path(), qs(), raw()) -> {ok, module(), arizona_live:route_opts(), request()} | error.

scheme/1

-callback scheme(raw()) -> scheme().

Functions

adapter/1

-spec adapter(request()) -> adapter().

Returns the adapter module that backs the request.

bindings/1

-spec bindings(request()) -> {bindings(), request()}.

Returns route-pattern path bindings. Lazy-loaded via the adapter on first access, cached in the returned request for subsequent calls.

body/1

-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).

clear_session(Request)

-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.

client_scheme(Request)

-spec client_scheme(Request) -> {scheme(), Request} when Request :: request().

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.

commit_session(Request)

-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.

cookies/1

-spec cookies(request()) -> {cookies(), request()}.

Returns parsed cookies as a proplist. Lazy-loaded and cached.

delete_session(Request, Key)

-spec delete_session(Request, Key) -> Request when Request :: request(), Key :: atom() | binary().

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.

flash(Request)

-spec flash(Request) -> flash() when Request :: request().

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).

flash_out(Request)

-spec flash_out(Request) -> flash() when Request :: request().

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.

flush(Request)

-spec flush(Request) -> [{binary(), binary()}] when Request :: request().

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.

get_session(Request, Key)

-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.

get_session(Request, Key, Default)

-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.

halt_reply(Request)

-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.

halted_redirect(Request)

-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.

headers/1

-spec headers(request()) -> {headers(), request()}.

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.

location_kind(Location)

-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: an http/https URL, 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 HTTP Location. 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:.

method/1

-spec method(request()) -> method().

Returns the HTTP method (e.g. ~"GET", ~"POST").

new(Adapter, Raw, Opts)

-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.

params/1

-spec params(request()) -> {params(), request()}.

Returns parsed query-string params as a proplist. Lazy-loaded and cached.

Preserves repeat keys (?tag=a&tag=b returns two entries).

path/1

-spec path(request()) -> path().

Returns the request path (no query string).

peer/1

-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.

put_flash(Request, Key, Value)

-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.

put_resp_cookie(Request, Name, Value, Opts)

-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().

put_resp_header(Request, Name, Value)

-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).

put_resp_status(Request, Status)

-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.

put_session(Request, Key, Value)

-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.

raw/1

-spec raw(request()) -> raw().

Returns the raw, transport-native request value the adapter wraps.

read_session(Request)

-spec read_session(Request) -> {session(), Request} when Request :: request().

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.

redirect(Request, Location)

-spec redirect(Request, Location) -> Request when Request :: request(), Location :: binary().

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.

redirect(Request, Status, Location)

-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.

request_id/1

-spec request_id(request()) -> binary() | undefined.

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.

resp_cookies(Request)

-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.

resp_headers(Request)

-spec resp_headers(Request) -> [{binary(), iodata()}] when Request :: request().

Returns the stashed response headers (newest first), or [].

resp_status(Request)

-spec resp_status(Request) -> resp_status() | undefined when Request :: request().

Returns the stashed response status, or undefined.

scheme/1

-spec scheme(request()) -> scheme().

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.

session(Request)

-spec session(Request) -> session() when Request :: request().

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.

session_error(Request)

-spec session_error(Request) -> {error, term()} | undefined when Request :: request().

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.

session_id(Request)

-spec session_id(Request) -> binary() | undefined when Request :: request().

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.

set_raw(Req, Raw)

-spec set_raw(request(), raw()) -> request().

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.

user_agent(Req0)

-spec user_agent(request()) -> {binary(), request()}.

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]).