nhttp_msg (nhttp_lib v1.0.0)

View Source

Shared message-level helpers for the HTTP/2 (RFC 9113) and HTTP/3 (RFC 9114) state machines.

These functions deal with the parts of a request/response that are identical on both wire formats once the framing layer has produced a list of {Name, Value} pseudo + regular header pairs:

The module is intentionally protocol-agnostic: errors are returned as plain atoms and the calling protocol wraps them into the relevant wire error format (atom for H2, descriptive binary for H3).

Summary

Functions

Assemble the canonical nhttp_lib:request/0 map from the pseudo plus regular header list emitted by the protocol decoders.

Assemble the canonical nhttp_lib:response/0 map. Version is stamped onto the map; reason is the empty binary (HTTP/2 and HTTP/3 do not carry a reason phrase, RFC 9113 §8.3.2, RFC 9114 §4.3.2).

When both :authority and Host are present they must carry the same value (RFC 9113 §8.3.1, RFC 9114 §4.3.1). An undefined component is treated as absent. Returns {error, protocol_error} on mismatch.

Validate the RFC 8441 / 9220 Extended CONNECT pseudo-header combination. The four arguments are :method, :protocol, :authority and the local settings map (peer settings on the server side). The peer must advertise SETTINGS_ENABLE_CONNECT_PROTOCOL=1 to enable the feature. Returns ok when the combination is acceptable (including the common case where :protocol is absent), or one of three reasons that the caller maps to the protocol-appropriate error payload

Find the first content-length header in Headers and parse it. Returns undefined when the header is absent or has a non-binary value (defensive guard kept from the H2 implementation).

Project the pseudo-header part of a request header list into typed components and return the regular headers in their original order. Unknown pseudo-headers (<<":", _>>) are dropped. The caller is expected to have already rejected them at validation time.

Project :status out of a response header list, returning the integer status and the regular headers in their original order. Unknown pseudo-headers are dropped (already rejected at validation).

Return the host header value or <<>> when missing. Used as the authority fallback when :authority is empty (RFC 9113 §8.3.1, RFC 9114 §4.3.1).

True iff every byte in Bin is an ASCII digit ($0..$9). Used by parse_content_length/1 to reject negative or otherwise malformed values before reaching binary_to_integer/1.

Parse a content-length header value. Returns the integer for a non-negative decimal binary, or undefined for the empty binary or any non-digit input. Never raises.

Check that the bytes received so far are consistent with the advertised content-length. undefined always passes. On the final frame the counts must match exactly; on intermediate frames the count must not exceed the advertised length.

Run the protocol-agnostic request header validation FSM (RFC 9113 §8.2 / RFC 9114 §4.2) and return a normalized intermediate shape that protocol-specific post-passes can inspect. The FSM enforces the rules that are byte-identical across HTTP/2 and HTTP/3

Trailers must not contain pseudo-headers (RFC 9113 §8.1, RFC 9114 §4.1). Returns ok or {error, pseudo_in_trailers}. Callers map the atom to their protocol's error payload.

The :scheme pseudo-header must be http or https on the wire (RFC 9113 §8.3.1, RFC 9114 §4.3.1). Returns {error, protocol_error} on any other value (including undefined). Callers map the atom to their preferred error payload.

Types

content_length_error()

-type content_length_error() :: content_length_mismatch.

extended_connect_error()

-type extended_connect_error() :: missing_authority | bad_method | not_enabled.

request_shape()

-type request_shape() ::
          #{method := binary(),
            scheme := binary(),
            path := binary(),
            authority := binary() | undefined,
            host := binary() | undefined,
            protocol := binary() | undefined,
            headers := nhttp_lib:headers()}.

request_shape_error()

-type request_shape_error() ::
          duplicate_pseudo | unknown_pseudo | pseudo_after_regular | forbidden_connection_header |
          multiple_host_headers | missing_required_pseudo | bad_wire_scheme | authority_host_mismatch.

trailers_error()

-type trailers_error() :: pseudo_in_trailers.

Functions

build_request(Version, Peer, Headers)

-spec build_request(nhttp_lib:version(), nhttp_lib:peer() | undefined, nhttp_lib:headers()) ->
                       nhttp_lib:request().

Assemble the canonical nhttp_lib:request/0 map from the pseudo plus regular header list emitted by the protocol decoders.

Version is stamped into the returned map; Peer is included as the peer key when not undefined. Authority falls back to the host header when :authority is empty (RFC 9113 §8.3.1, RFC 9114 §4.3.1). The Extended CONNECT :protocol (RFC 8441 / 9220) is added when present.

build_response(Version, Headers)

-spec build_response(nhttp_lib:version(), nhttp_lib:headers()) -> nhttp_lib:response().

Assemble the canonical nhttp_lib:response/0 map. Version is stamped onto the map; reason is the empty binary (HTTP/2 and HTTP/3 do not carry a reason phrase, RFC 9113 §8.3.2, RFC 9114 §4.3.2).

check_authority_host_match/2

-spec check_authority_host_match(binary() | undefined, binary() | undefined) ->
                                    ok | {error, protocol_error}.

When both :authority and Host are present they must carry the same value (RFC 9113 §8.3.1, RFC 9114 §4.3.1). An undefined component is treated as absent. Returns {error, protocol_error} on mismatch.

check_extended_connect/4

-spec check_extended_connect(binary() | undefined, binary() | undefined, binary() | undefined, map()) ->
                                ok | {error, extended_connect_error()}.

Validate the RFC 8441 / 9220 Extended CONNECT pseudo-header combination. The four arguments are :method, :protocol, :authority and the local settings map (peer settings on the server side). The peer must advertise SETTINGS_ENABLE_CONNECT_PROTOCOL=1 to enable the feature. Returns ok when the combination is acceptable (including the common case where :protocol is absent), or one of three reasons that the caller maps to the protocol-appropriate error payload:

  • missing_authority: CONNECT without :authority.
  • not_enabled: peer has not advertised enable_connect_protocol.
  • bad_method: :protocol present with a method other than CONNECT.

extract_content_length/1

-spec extract_content_length(nhttp_lib:headers()) -> non_neg_integer() | undefined.

Find the first content-length header in Headers and parse it. Returns undefined when the header is absent or has a non-binary value (defensive guard kept from the H2 implementation).

extract_request_pseudo(Headers)

-spec extract_request_pseudo(nhttp_lib:headers()) ->
                                {nhttp_lib:method() | undefined,
                                 binary(),
                                 nhttp_lib:scheme(),
                                 nhttp_lib:authority(),
                                 binary() | undefined,
                                 nhttp_lib:headers()}.

Project the pseudo-header part of a request header list into typed components and return the regular headers in their original order. Unknown pseudo-headers (<<":", _>>) are dropped. The caller is expected to have already rejected them at validation time.

extract_response_pseudo(Headers)

-spec extract_response_pseudo(nhttp_lib:headers()) -> {nhttp_lib:status() | 0, nhttp_lib:headers()}.

Project :status out of a response header list, returning the integer status and the regular headers in their original order. Unknown pseudo-headers are dropped (already rejected at validation).

host_header_or_empty(Headers)

-spec host_header_or_empty(nhttp_lib:headers()) -> nhttp_lib:authority().

Return the host header value or <<>> when missing. Used as the authority fallback when :authority is empty (RFC 9113 §8.3.1, RFC 9114 §4.3.1).

is_digits/1

-spec is_digits(binary()) -> boolean().

True iff every byte in Bin is an ASCII digit ($0..$9). Used by parse_content_length/1 to reject negative or otherwise malformed values before reaching binary_to_integer/1.

parse_content_length/1

-spec parse_content_length(binary()) -> non_neg_integer() | undefined.

Parse a content-length header value. Returns the integer for a non-negative decimal binary, or undefined for the empty binary or any non-digit input. Never raises.

validate_content_length/3

-spec validate_content_length(non_neg_integer() | undefined, non_neg_integer(), nhttp_lib:fin()) ->
                                 ok | {error, content_length_error()}.

Check that the bytes received so far are consistent with the advertised content-length. undefined always passes. On the final frame the counts must match exactly; on intermediate frames the count must not exceed the advertised length.

validate_request_pseudo_shape(Headers)

-spec validate_request_pseudo_shape(nhttp_lib:headers()) ->
                                       {ok, request_shape()} | {error, request_shape_error()}.

Run the protocol-agnostic request header validation FSM (RFC 9113 §8.2 / RFC 9114 §4.2) and return a normalized intermediate shape that protocol-specific post-passes can inspect. The FSM enforces the rules that are byte-identical across HTTP/2 and HTTP/3:

  • Pseudo-header block precedes the regular fields.
  • No duplicate pseudo-headers and no unknown pseudo names.
  • No hop-by-hop / connection-specific fields (Connection, Keep-Alive, Proxy-Connection, Transfer-Encoding, Upgrade).
  • At most one Host field.
  • :method, :scheme and a non-empty :path are present.
  • :scheme is http or https on the wire.
  • When both :authority and Host are present they must match. Protocol-specific rules are deferred (TE policy per RFC 9113 §8.2.2 vs RFC 9114 §4.2, HTTP/3's "authority-requiring scheme needs :authority or Host" rule, and Extended CONNECT settings). Callers use request_shape() plus their own settings to finish validation.

validate_trailers/1

-spec validate_trailers(nhttp_lib:headers()) -> ok | {error, trailers_error()}.

Trailers must not contain pseudo-headers (RFC 9113 §8.1, RFC 9114 §4.1). Returns ok or {error, pseudo_in_trailers}. Callers map the atom to their protocol's error payload.

validate_wire_scheme/1

-spec validate_wire_scheme(binary() | undefined) -> ok | {error, protocol_error}.

The :scheme pseudo-header must be http or https on the wire (RFC 9113 §8.3.1, RFC 9114 §4.3.1). Returns {error, protocol_error} on any other value (including undefined). Callers map the atom to their preferred error payload.