roadrunner_http (roadrunner v0.6.0)

View Source

Protocol-version-agnostic HTTP semantics shared by HTTP/1.1 (roadrunner_http1) and HTTP/2 (roadrunner_http2_*) modules.

What lives here is RFC 9110 semantics — types and helpers whose meaning doesn't depend on wire framing — not RFC 9112 syntax. The HTTP/1.1 wire codec (request-line / header / chunked parsers, status-line + CRLF response encoder) stays in roadrunner_http1. HTTP/2 frame codec + HPACK live in their own modules.

Items here:

  • Header list shape: [{binary(), binary()}].
  • HTTP status codes: 100..599 and the redirect subset.
  • Protocol version tuple: {Major, Minor}.
  • IMF-fixdate formatter (http_date_now/0 for the current time, format_http_date/1 for an arbitrary posix timestamp) for the Date response header per RFC 9110 §5.6.7 and the Last-Modified response header used by the static handler.
  • Header field-value safety (RFC 9110 §5.5): reject CR/LF/NUL in a header name or value before it reaches the wire (check_header_safe/2), shared by every version's response path.
  • Connection-specific field stripping (RFC 9113 §8.2.2 / RFC 9114 §4.2): drop the hop-by-hop fields HTTP/2 and HTTP/3 MUST NOT generate from a response field section (strip_connection_specific_fields/1), or do it fused with the field-value check in a single pass (strip_connection_specific_fields_safe/1); HTTP/1.1 honours these fields, so it does not strip.

roadrunner_http1 and roadrunner_req re-export the primitive types as aliases so existing callers keep compiling unchanged. The request map shape lives in roadrunner_req alongside the accessors that operate on it.

Summary

Functions

Inject the framework's automatic response headers for an HTTP/1 or HTTP/2 (TCP) response: Date always (RFC 9110 §6.6.1) plus Alt-Svc advertising the listener's HTTP/3 endpoint (RFC 7838) when it co-serves h3 on a fixed port. The caller passes the precomputed Alt-Svc value (cached on the connection loop record), or undefined when no h3 is co-served. HTTP/3 responses use with_date/1 directly — a client already on h3 needs no Alt-Svc.

Validate that a header name or value contains no CR, LF, or NUL — the bytes that would let an attacker who controls the value inject new headers (or terminate the header block early). Crashes with {header_injection, Kind, Bin} when an unsafe byte is present.

Format a posix timestamp (seconds since epoch) as an IMF-fixdate per RFC 9110 §5.6.7. Same shape as http_date_now/0 but for an explicit timestamp — used by the static file handler to emit the Last-Modified header for a file's mtime.

Format the current UTC time as an IMF-fixdate per RFC 9110 §5.6.7 — the canonical HTTP Date header format, e.g. Sun, 06 Nov 1994 08:49:37 GMT. Used by the dispatch layer to auto-inject the Date response header per RFC 9110 §6.6.1.

Drop connection-specific header fields from an HTTP/2 or HTTP/3 response field section. RFC 9113 §8.2.2 and RFC 9114 §4.2 forbid generating connection, keep-alive, proxy-connection, transfer-encoding, or upgrade over those protocols — they are hop-by-hop fields (RFC 9110 §7.6.1) meaningful only on an HTTP/1.1 connection. A handler shared across protocols may set one (e.g. connection: close, idiomatic on h1); stripping it on h2/h3 keeps that handler working while staying conformant, since the framework never puts the field on the wire. HTTP/1.1 honours these fields, so its response path does not strip.

Single-pass combination of check_header_safe/2 and strip_connection_specific_fields/1 for the response paths that crash on injection (the HTTP/2 conn loop and the HTTP/3 trailer path): in one traversal it rejects CR/LF/NUL in any name or value (crashing with {header_injection, Kind, Bin}, like check_header_safe/2) and drops the connection-specific fields h2/h3 MUST NOT generate. HTTP/3 response headers answer 500 on injection instead of crashing, so they run the non-crashing check and strip_connection_specific_fields/1 separately.

Inject a Date response header (RFC 9110 §6.6.1) unless the handler already set one. Shared by the HTTP/1, HTTP/2, and HTTP/3 response paths so every response carries Date from the one cached clock (http_date_now/0). RFC 9110 makes Date a MUST on 2xx/3xx/4xx and a MAY on 1xx/5xx, so injecting it unconditionally is conformant.

Types

headers()

-type headers() :: [{Name :: binary(), Value :: binary()}].

redirect_status()

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

status()

-type status() :: 100..599.

version()

-type version() :: {1, 0} | {1, 1} | {2, 0} | {3, 0}.

Functions

auto_headers(Headers, AltSvc)

-spec auto_headers(headers(), binary() | undefined) -> headers().

Inject the framework's automatic response headers for an HTTP/1 or HTTP/2 (TCP) response: Date always (RFC 9110 §6.6.1) plus Alt-Svc advertising the listener's HTTP/3 endpoint (RFC 7838) when it co-serves h3 on a fixed port. The caller passes the precomputed Alt-Svc value (cached on the connection loop record), or undefined when no h3 is co-served. HTTP/3 responses use with_date/1 directly — a client already on h3 needs no Alt-Svc.

check_header_safe(Bin, Kind)

-spec check_header_safe(binary(), name | value) -> ok.

Validate that a header name or value contains no CR, LF, or NUL — the bytes that would let an attacker who controls the value inject new headers (or terminate the header block early). Crashes with {header_injection, Kind, Bin} when an unsafe byte is present.

Public so any response path emitting a single header (e.g. roadrunner_stream_response for chunked-response trailers) can run the check before writing to the wire.

format_http_date(Posix)

-spec format_http_date(integer()) -> binary().

Format a posix timestamp (seconds since epoch) as an IMF-fixdate per RFC 9110 §5.6.7. Same shape as http_date_now/0 but for an explicit timestamp — used by the static file handler to emit the Last-Modified header for a file's mtime.

http_date_now()

-spec http_date_now() -> binary().

Format the current UTC time as an IMF-fixdate per RFC 9110 §5.6.7 — the canonical HTTP Date header format, e.g. Sun, 06 Nov 1994 08:49:37 GMT. Used by the dispatch layer to auto-inject the Date response header per RFC 9110 §6.6.1.

Built via direct bit-syntax binary construction rather than io_lib:format/2 because the shape is fixed (RFC 9110 mandates exact widths and the day/month abbreviations) and this function runs on the response hot path.

Cached per process in the process dictionary, keyed by the current Posix second: the formatted binary is identical for every response a process emits within the same second, so we recompute it only when the second ticks over. Per-process rather than via persistent_term because the value changes every second, and a persistent_term:put that frequent forces a global scan of every process heap on the response hot path; the per-process cache pays a cheap dictionary read instead, and reformats at most once per second per process: once per connection on h1/h2, once per request on h3 (its stream workers are per-request).

strip_connection_specific_fields(Headers)

-spec strip_connection_specific_fields(headers()) -> headers().

Drop connection-specific header fields from an HTTP/2 or HTTP/3 response field section. RFC 9113 §8.2.2 and RFC 9114 §4.2 forbid generating connection, keep-alive, proxy-connection, transfer-encoding, or upgrade over those protocols — they are hop-by-hop fields (RFC 9110 §7.6.1) meaningful only on an HTTP/1.1 connection. A handler shared across protocols may set one (e.g. connection: close, idiomatic on h1); stripping it on h2/h3 keeps that handler working while staying conformant, since the framework never puts the field on the wire. HTTP/1.1 honours these fields, so its response path does not strip.

strip_connection_specific_fields_safe(Headers)

-spec strip_connection_specific_fields_safe(headers()) -> headers().

Single-pass combination of check_header_safe/2 and strip_connection_specific_fields/1 for the response paths that crash on injection (the HTTP/2 conn loop and the HTTP/3 trailer path): in one traversal it rejects CR/LF/NUL in any name or value (crashing with {header_injection, Kind, Bin}, like check_header_safe/2) and drops the connection-specific fields h2/h3 MUST NOT generate. HTTP/3 response headers answer 500 on injection instead of crashing, so they run the non-crashing check and strip_connection_specific_fields/1 separately.

with_date(Headers)

-spec with_date(headers()) -> headers().

Inject a Date response header (RFC 9110 §6.6.1) unless the handler already set one. Shared by the HTTP/1, HTTP/2, and HTTP/3 response paths so every response carries Date from the one cached clock (http_date_now/0). RFC 9110 makes Date a MUST on 2xx/3xx/4xx and a MAY on 1xx/5xx, so injecting it unconditionally is conformant.