roadrunner_req (roadrunner v0.1.0)

View Source

Pure accessors over the request() map.

Decouples handler code from the underlying map shape — handlers should prefer these functions over direct maps:get/2 so the request representation can evolve without breaking them.

The request/0 type is canonical here. The shared HTTP primitives re-exported alongside it (headers/0, version/0, status/0, redirect_status/0) are aliases of the definitions in roadrunner_http.

Summary

Types

Body-read envelope attached to the request in body_buffering => manual mode. read_body/1,2 consumes from it; the conn owns the recv closure and tracks how much remains.

Framework-internal h1-parser hot-path optimization carried on the request map's cached_decisions field. Handlers should ignore both the field and this type — they're absent on h2 requests and on manually-built request maps. Populated by the h1 parser at request-read time and consumed by framework code to skip per-request header re-lowercasing on the dispatch hot path.

The parsed request map handlers receive.

Functions

Return the router-captured bindings for this request as a #{Name => Value} map of binaries.

Return the buffered request body bytes as seen by the handler.

Return the leftmost client identifier from the Forwarded header (RFC 7239) or, if absent, from X-Forwarded-For. Returns undefined when neither header is set or the Forwarded header has no for= parameter.

Return whether the request carries a non-empty body.

Return whether Name is present in the request headers.

Look up a single header value by name. Returns undefined if absent.

Return the full ordered list of {Name, Value} header pairs.

Return the request method (uppercase ASCII binary).

Return whether the request method matches the given binary.

Parse the Cookie request header into a list of {Name, Value} pairs via roadrunner_cookie:parse/1.

Parse the query string portion of the request target into a list of {Key, Value} pairs (or {Key, true} for bare flags) via roadrunner_qs:parse/1.

Return the path component of the request-target.

Return the TCP peer ({IpAddress, Port}) for the connection that delivered this request.

Return the raw query string portion of the request-target, without the leading ?. Empty binary when no ? is present (or nothing follows it).

Read the request body in one shot. Works in both auto and manual body-buffering modes

Read the request body, optionally bounded by length.

Read the next decoded HTTP chunk from a chunked-encoded request body.

Read and parse a form-encoded request body. Inspects the request's Content-Type and dispatches

Return the per-request correlation token attached by roadrunner_conn.

Return the connection scheme — http for plain TCP, https for TLS.

Return the opaque per-route handler state attached at compile time.

Return the HTTP version tuple ({1,0} or {1,1}).

Types

body_reader()

-type body_reader() ::
          #{framing := none | chunked | {content_length, non_neg_integer()},
            buffered := binary(),
            bytes_read := non_neg_integer(),
            pending := binary(),
            done := boolean(),
            recv := fun(() -> {ok, binary()} | {error, term()}),
            max := non_neg_integer()}.

Body-read envelope attached to the request in body_buffering => manual mode. read_body/1,2 consumes from it; the conn owns the recv closure and tracks how much remains.

pending holds decoded body bytes that have been parsed off the wire but not yet handed to the caller — used for chunked framing to absorb a chunk's payload across multiple length-bounded calls. done flips true once the size-0 last chunk is parsed.

cached_decisions()

-type cached_decisions() ::
          #{is_chunked := boolean(),
            has_transfer_encoding := boolean(),
            expects_continue := boolean(),
            connection_lower := binary(),
            content_length := none | {ok, non_neg_integer()} | {error, bad_content_length},
            has_host := boolean()}.

Framework-internal h1-parser hot-path optimization carried on the request map's cached_decisions field. Handlers should ignore both the field and this type — they're absent on h2 requests and on manually-built request maps. Populated by the h1 parser at request-read time and consumed by framework code to skip per-request header re-lowercasing on the dispatch hot path.

headers()

-type headers() :: roadrunner_http:headers().

request()

-type request() ::
          #{method := binary(),
            target := binary(),
            version := version(),
            headers := headers(),
            cached_decisions => cached_decisions(),
            body => iodata(),
            bindings => roadrunner_router:bindings(),
            peer => {inet:ip_address(), inet:port_number()} | undefined,
            scheme => http | https,
            state => term(),
            body_reader => body_reader(),
            request_id => binary(),
            listener_name => atom()}.

The parsed request map handlers receive.

Required fields are present on every request the framework delivers; optional fields are populated by the framework when applicable (e.g. body in body_buffering => auto mode, bindings for routed dispatch, request_id for telemetry).

The same shape flows through both the h1 and h2 paths — h2's roadrunner_http2_request synthesizes a request matching this type from frames + pseudo-headers, so handlers don't have to care which protocol delivered the bytes.

version()

-type version() :: roadrunner_http:version().

Functions

bindings/1

-spec bindings(request()) -> roadrunner_router:bindings().

Return the router-captured bindings for this request as a #{Name => Value} map of binaries.

roadrunner_conn populates this from roadrunner_router:match/2 before invoking the handler. Empty map when the listener is in single-handler mode (no router) or the matched route has no :param segments.

body/1

-spec body(request()) -> iodata().

Return the buffered request body bytes as seen by the handler.

The connection process embeds whatever bytes followed the header block under the body map key before invoking the handler. Auto-mode delivers the full body as iodata() (an iolist of recv chunks for multi-chunk bodies, a single binary otherwise) so handlers that only need iolist_size/1 or gen_tcp:send/2 skip a flatten. Handlers requiring a flat binary call iolist_to_binary/1 themselves.

Returns <<>> when the request has no body field (e.g. when a request map is constructed manually outside the connection pipeline).

forwarded_for(Req)

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

Return the leftmost client identifier from the Forwarded header (RFC 7239) or, if absent, from X-Forwarded-For. Returns undefined when neither header is set or the Forwarded header has no for= parameter.

The returned binary is whatever the proxy chose to put there — for RFC 7239 that's typically an IP literal (192.0.2.60) or a quoted IPv6+port ([2001:db8::1]:4711); for X-Forwarded-For it's conventionally just the IP. The caller decides how to parse it.

No trust list is enforced. Anyone who can speak to the listener directly can spoof these headers — only call this when the deploy sits behind a trusted reverse proxy that strips/overwrites them.

has_body/1

-spec has_body(request()) -> boolean().

Return whether the request carries a non-empty body.

Returns false for both an absent body field and an empty body. Handlers can use this as a short-circuit before doing any body-aware work. Uses iolist_size/1 so the check is O(length of iolist), not O(total bytes).

has_header/2

-spec has_header(binary(), request()) -> boolean().

Return whether Name is present in the request headers.

Lookup is case-insensitive on Name — same convention as header/2.

header/2

-spec header(binary(), request()) -> binary() | undefined.

Look up a single header value by name. Returns undefined if absent.

The lookup is case-insensitive on Name — the parser already lowercases header names on the wire, so any-case input is normalized before searching.

headers/1

-spec headers(request()) -> headers().

Return the full ordered list of {Name, Value} header pairs.

method/1

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

Return the request method (uppercase ASCII binary).

method_is/2

-spec method_is(binary(), request()) -> boolean().

Return whether the request method matches the given binary.

Comparison is byte-exact and case-sensitive — the parser already enforces uppercase methods on the wire, so callers should pass uppercase too (~"GET", ~"POST", etc.).

parse_cookies(Req)

-spec parse_cookies(request()) -> [{binary(), binary()}].

Parse the Cookie request header into a list of {Name, Value} pairs via roadrunner_cookie:parse/1.

Returns [] when the request carries no Cookie header.

parse_qs(Req)

-spec parse_qs(request()) -> [{binary(), binary() | true}].

Parse the query string portion of the request target into a list of {Key, Value} pairs (or {Key, true} for bare flags) via roadrunner_qs:parse/1.

Returns [] when the target has no query component.

path/1

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

Return the path component of the request-target.

If the target contains a ? query separator, only the bytes before it are returned. The path is not percent-decoded — that's the router's job.

peer/1

-spec peer(request()) -> {inet:ip_address(), inet:port_number()} | undefined.

Return the TCP peer ({IpAddress, Port}) for the connection that delivered this request.

roadrunner_conn populates this from inet:peername/1 once per connection. Returns undefined when the request map has no peer field (e.g. constructed manually outside the connection pipeline) or when the OS call failed at accept time.

qs/1

-spec qs(request()) -> binary().

Return the raw query string portion of the request-target, without the leading ?. Empty binary when no ? is present (or nothing follows it).

For decoded {Key, Value} pairs, pipe through roadrunner_qs:parse/1.

read_body(Req)

-spec read_body(request()) -> {ok, iodata(), request()} | {error, term()}.

Read the request body in one shot. Works in both auto and manual body-buffering modes:

  • auto (default): the conn already buffered the body before invoking the handler — this returns the buffered bytes unchanged. Req is returned as-is.
  • manual: the conn parked the body on the socket. This drains it and returns the bytes. The returned Req2 carries the updated body-read state — to enable keep-alive on the same connection in manual mode, hand Req2 back via the 4-tuple handler return shape {Status, Headers, Body, Req2} so the conn can drain whatever the handler skipped.

read_body/2

-spec read_body(request(), #{length => non_neg_integer()}) ->
                   {ok, iodata(), request()} | {more, iodata(), request()} | {error, term()}.

Read the request body, optionally bounded by length.

Opts may contain length => non_neg_integer(). If absent, behaves like read_body/1 (drain to end). When set, returns up to Length bytes per call:

  • {ok, Bytes, Req2} — body is fully drained (no more bytes left).
  • {more, Bytes, Req2} — more bytes remain; call again with Req2.

Works for both content-length and chunked framing — chunked bodies are streamed transparently across chunk boundaries up to Length bytes per call.

In auto mode the body is already buffered, so length has no effect — the buffered bytes are returned in one shot.

read_body_chunked/1

-spec read_body_chunked(request()) ->
                           {ok, iodata(), request()} | {more, iodata(), request()} | {error, term()}.

Read the next decoded HTTP chunk from a chunked-encoded request body.

Mirrors cowboy's chunk-at-a-time read pattern: each call returns one chunk's payload. {more, Bytes, Req2} means more chunks remain; {ok, <<>>, Req2} signals end-of-body (the size-0 last chunk has been seen).

For non-chunked framing (auto mode, content-length, or no body), read_body_chunked/1 falls through to read_body/1's behavior — the buffered body comes back in one shot. The "chunk boundary" concept only applies to wire-level chunked transfer encoding.

Threading is the same as read_body/1,2: hand Req2 back to the conn via the {Response, Req2} handler return shape so any unread chunks get drained for keep-alive.

read_form(Req)

-spec read_form(request()) ->
                   {ok, urlencoded, [{binary(), binary() | true}], request()} |
                   {ok, multipart, [roadrunner_multipart:part()], request()} |
                   {error, no_content_type | unsupported_content_type | no_boundary | term()}.

Read and parse a form-encoded request body. Inspects the request's Content-Type and dispatches:

  • application/x-www-form-urlencoded[; …]{ok, urlencoded, [{Name, Value | true}], Req2}. Values are percent-decoded (and + → space) per roadrunner_qs:parse/1. Bare flags come back as {Name, true}.
  • multipart/form-data; boundary=…{ok, multipart, [Part], Req2} where each Part is the map returned by roadrunner_multipart:parse/2 (#{headers, body}). {error, no_boundary} if the boundary parameter is missing.

Other content types return {error, unsupported_content_type}; absent Content-Type returns {error, no_content_type}. Reads the body via read_body/1 (so works in both auto and manual buffering modes), and threads Req2 back so trailing body bytes get drained on keep-alive.

request_id/1

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

Return the per-request correlation token attached by roadrunner_conn.

16 lowercase hex chars (8 bytes of CSPRNG output), unique per request even on the same keep-alive connection. Mirrored into the conn process's logger metadata, so any ?LOG_* call in middleware or the handler is automatically annotated with the same id.

undefined for manually-constructed request maps used in tests.

scheme/1

-spec scheme(request()) -> http | https.

Return the connection scheme — http for plain TCP, https for TLS.

roadrunner_conn sets this once per connection from the transport tag. Defaults to http for request maps constructed manually outside the connection pipeline.

state/1

-spec state(request()) -> term().

Return the opaque per-route handler state attached at compile time.

Sources, listed by route shape:

  • 3-tuple {Path, Handler, State} or map #{path, handler, state} list entry.
  • Listener single-handler {Module, State} tuple or #{handler, state, ...} map.

Returns undefined for shapes that don't carry state (2-tuple route, bare-atom single-handler, map without a state key).

version/1

-spec version(request()) -> version().

Return the HTTP version tuple ({1,0} or {1,1}).