nhttp_h1 (nhttp_lib v1.1.1)

View Source

HTTP/1.1 request and response codec.

This module parses and encodes HTTP/1.1 messages (RFC 9112).

Parsing

Parsing functions return {ok, Result, BytesConsumed} where BytesConsumed is the number of bytes consumed from the input. Use split_at/2 to get the remaining buffer:

{ok, Request, Consumed} = nhttp_h1:parse_request(Binary),
Rest = nhttp_h1:split_at(Binary, Consumed).

No remainder binary is built until the caller asks for one with split_at/2.

For incomplete data, parsing returns {more, MinBytes} where MinBytes is a hint for how many more bytes might be needed.

Options

The opts() map supports the following limits:

  • max_header_size - Maximum total size of all headers in bytes (default: infinity)
  • max_headers_count - Maximum number of headers (default: infinity)
  • max_body_size - Maximum body size in bytes (default: infinity)

When a limit is exceeded, parsing returns {error, header_too_large}, {error, too_many_headers}, or {error, {body_too_large, Size, Max}} respectively.

With max_header_size set, an incomplete request head is also rejected as header_too_large once the buffered input exceeds max_header_size plus an 8 KiB request-line allowance: a head that never terminates (for example a header line with no CRLF) cannot grow the caller's buffer without bound. The same budget bounds chunked trailer sections, and chunk-size lines longer than 1 KiB are rejected as invalid_chunk_size.

Opts = #{max_header_size => 8192, max_headers_count => 100, max_body_size => 1048576},
case nhttp_h1:parse_request(Binary, Opts) of
    {ok, Request, Consumed} -> handle_request(Request);
    {error, header_too_large} -> respond_413();
    {error, too_many_headers} -> respond_431();
    {error, {body_too_large, _Size, _Max}} -> respond_413()
end.

Encoding

A caller that sends the same field lines on many messages validates them once with prepare_headers/1 and passes the result through enc_opts/0:

{ok, Static} = nhttp_h1:prepare_headers(StaticFields),
{ok, Io} = nhttp_h1:encode_response(Resp, #{prepared => Static}).

A prepared block is validated once, the field lines of each message are validated again, and no octet reaches the output unread. The derived Content-Length comes first, then the prepared block, then the field lines of this message.

A caller that holds no block to reuse calls the encoder directly:

{ok, IOList} = nhttp_h1:encode_request(Request).
{ok, IOList} = nhttp_h1:encode_response(Response).

The encoders validate what they serialise and return {error, t:encode_error/0} for a field name that is not a token, a field value or reason phrase that carries CR, LF, NUL, or another control byte, and a request target that carries a byte at or below 0x20. RFC 9112 Section 11.1 names that filter as the mitigation for response splitting and request smuggling. A refused message is never repaired and never truncated.

encode_request/1 and encode_response/1 consume the canonical nhttp_lib:request/0 / nhttp_lib:response/0 map shape. The body field is for the convenience case where the whole payload fits in memory: it is emitted inline after the header block and a Content-Length is derived if neither Content-Length nor Transfer-Encoding is present in headers.

For streaming bodies, do not populate body in the map. Send the header block first via encode_response_head/3, then emit each chunk via encode_chunk/1, then close the body with encode_last_chunk/0 (set Transfer-Encoding: chunked in the headers). A message that carries a trailer section closes with encode_trailers/1 in place of encode_last_chunk/0. The same staged pattern applies to chunked requests.

Summary

Types

Encoder options for encode_response/2.

Reason an encoder refuses to serialise a message.

A field block that prepare_headers/1 validated once.

Functions

Compute the response body framing mode from the request method, response status, and response headers (RFC 9112 §6.3). Pure helper: pass the values parsed by parse_response_headers/1,2 plus the method of the matching request. The returned body_stream() is fed into parse_response_body/2. Framing rules in order

Encode a chunk for chunked transfer encoding.

Encode the final (zero-length) chunk.

Encode an HTTP/1.1 request to iolist. The encoder refuses a message that cannot be framed unambiguously on the wire. It returns {error, {invalid_request_target, Target}} for a target that is empty or that carries a byte at or below 0x20 or the byte 0x7F, {error, {invalid_field_name, Name}} for a field name that is not a token (RFC 9110 Section 5.6.2), and {error, {invalid_field_value, Value}} for a field value that carries CR, LF, NUL, another control byte, or 0x7F (RFC 9110 Section 5.5). RFC 9112 Section 2.2 forbids a sender to generate a bare CR in any protocol element other than the content, and Section 11.1 names this filtering as the mitigation for request smuggling and response splitting. No value is repaired and no byte is stripped. The message is refused whole.

Encode an HTTP/1.1 request to iolist under the given encoder options. See encode_request/1 for the framing rules and the rejected byte classes. The request path reads the prepared key of enc_opts/0 only. A request carries a derived Content-Length when its body is not empty, so content_length has no meaning here.

Encode an HTTP/1.1 response to iolist. Equivalent to encode_response(Resp, #{}). The encoder adds Content-Length when the header list carries neither content-length nor transfer-encoding, including a Content-Length: 0 on an empty body. The encoder adds no Content-Length at a 1xx, 204, or 304 status. RFC 9110 Section 8.6 forbids the field at 1xx and 204. It permits the field at 304 only at the length that a 200 response would have carried, which this encoder cannot compute, so a caller that knows the value supplies it in the header list. A 2xx response to a CONNECT request also carries no Content-Length. The response map holds no request method, so that case needs encode_response/2 with #{content_length => omit}. The encoder refuses a message that cannot be framed unambiguously on the wire. It returns {error, {invalid_reason_phrase, Reason}} for a reason phrase outside 1*( HTAB / SP / VCHAR / obs-text ) (RFC 9112 Section 4.1), {error, {invalid_field_name, Name}} for a field name that is not a token (RFC 9110 Section 5.6.2), and {error, {invalid_field_value, Value}} for a field value that carries CR, LF, NUL, another control byte, or 0x7F (RFC 9110 Section 5.5). An empty reason phrase stays legal, because the status-line grammar makes the element optional. RFC 9112 Section 2.2 forbids a sender to generate a bare CR in any protocol element other than the content, and Section 11.1 names this filtering as the mitigation for response splitting. No value is repaired and no byte is stripped. The message is refused whole.

Encode an HTTP/1.1 response to iolist under the given encoder options. See encode_response/1 for the framing rules, the rejected byte classes, and enc_opts/0 for the options. A trailers key that holds a non-empty field list frames the body with the chunked transfer coding: the header block, one chunk that carries the whole body, then the last chunk, the trailer section and the closing CRLF. The header list must already carry a Transfer-Encoding whose final coding is chunked, because RFC 9110 Section 6.5.1 makes a trailer section possible only when an explicit framing mechanism enables it, and RFC 9112 Section 7.1.2 names the chunked transfer coding as that mechanism for HTTP/1.1. A response framed any other way holds no position for the field lines, so the encoder returns {error, {trailers_require_chunked, Trailers}} rather than drop them. encode_trailers/1 states which trailer field names the encoder refuses. A prepared block travels ahead of the field lines of this message. A response that carries a trailer section keeps its Transfer-Encoding in the message header list, because require_chunked_framing/2 reads that list and a prepared block records only that a framing field is present, never which transfer coding is final.

Encode HTTP/1.x response headers for streaming. Used when sending chunked responses - sends status line + headers only. The reason phrase comes from the status code, so only the header list is subject to validation. See encode_response/1 for the rejected byte classes.

Encode HTTP/1.x response headers for streaming, under the given encoder options. The head path reads the prepared key of enc_opts/0 only. A streaming response derives no Content-Length, so content_length has no meaning here. The prepared block travels ahead of the field lines of this message.

Encode the terminating sequence of a chunked message, with a trailer section. The return carries the last chunk, the trailer field lines and the single CRLF that ends chunked-body (RFC 9112 Section 7.1: chunked-body = *chunk last-chunk trailer-section CRLF). A caller emits it in place of encode_last_chunk/0, and encode_trailers([]) writes exactly what encode_last_chunk/0 writes. A trailer field is validated as a header field is. The encoder returns {error, {invalid_field_name, Name}} for a name that is not a token (RFC 9110 Section 5.6.2) and {error, {invalid_field_value, Value}} for a value that carries CR, LF, NUL, another control byte, or 0x7F (RFC 9110 Section 5.5). The encoder also refuses the names transfer-encoding, content-length, host and trailer with {error, {forbidden_trailer_field, Name}}. RFC 9112 Section 7.1.3 has a recipient compute the content length and rewrite Transfer-Encoding at the point where the trailer section arrives, so a recipient that merges one of those fields into the header section holds two contradictory framing statements for one message. RFC 9112 Section 11.1 names that filtering as the mitigation for request smuggling and response splitting. The four names are a defence against a recipient that merges in breach of RFC 9110 Section 6.5.1. They are not an RFC enumeration, and the list does not grow. RFC 9110 Section 6.5.1 puts the general rule on the sender: generate a trailer field only when the definition of that field name permits trailer use. No registry records that permission, so the encoder cannot check it and the caller owns it. RFC 9110 Section 6.6.2 asks a sender that intends to write a trailer section to announce the names in a Trailer header field.

Signal end-of-stream for a response body parse driven by parse_response_body/2. Used to terminate until_close framing when the underlying transport closes, and to surface mid-body framing errors for {length, _} and {chunked, _}.

Parse an HTTP/1.1 request from binary. Returns {ok, Request, BytesConsumed} on success. Use split_at/2 to get the remaining buffer.

Parse HTTP/1.1 request with options. Options can include: max_header_size, max_headers_count, max_body_size.

Feed body bytes for a streaming request whose headers were parsed via parse_request_headers/1,2. Returns one of

Parse HTTP/1.1 request headers only, without consuming the body, enforcing the supplied limits. Returns {ok, Request, BodyStream, BytesConsumed}. The body framing mode is encoded in BodyStream

Parse an HTTP/1.1 response from binary. Returns {ok, Response, BytesConsumed} on success. Use split_at/2 to get the remaining buffer.

Parse HTTP/1.1 response with options. Options can include: max_header_size, max_headers_count, max_body_size.

Feed body bytes for a streaming response whose headers were parsed via parse_response_headers/1,2 and whose framing mode was selected via body_stream_from_response/3. For none and {length, 0}, returns {ok, [{fin, []}], none, 0}. For {length, N>0} and {chunked, _}, behaves identically to parse_request_body/2. For until_close, emits [{data, _}] for whatever bytes are in the buffer and keeps the stream open. The caller must signal EOF via finalize_response_body/1 when the underlying transport closes.

Parse HTTP/1.1 response headers only, like parse_response_headers/2, but also return the reason phrase and protocol version from the status line. Returns {ok, Status, Reason, Version, Headers, Rest} where Rest is the binary after the headers. Used by streaming callers that must preserve the full response head (status, reason, version) while reading the body separately via parse_response_body/2.

Parse HTTP/1.1 response headers only, without body, enforcing the supplied limits (max_header_size, max_headers_count).

Validate a field list once and return the field lines as one binary. The encoders read every octet of every field name and every field value on every call, because RFC 9112 Section 11.1 names that filtering as the mitigation for request smuggling and response splitting. A caller that sends the same field lines on many messages pays for the same octets on every message. prepare_headers/1 moves that cost to one call.

Split buffer at position, returning the remainder.

Types

body_chunk()

-type body_chunk() :: {data, binary()} | {fin, nhttp_lib:headers()} | {abort, nhttp_lib:error()}.

body_mode()

-type body_mode() :: undefined | {content_length, non_neg_integer()} | chunked.

body_stream()

-type body_stream() :: {chunked, chunked_st()} | {length, non_neg_integer()} | until_close | none.

chunked_st()

-opaque chunked_st()

enc_opts()

-type enc_opts() :: #{content_length => auto | omit, prepared => prepared()}.

Encoder options for encode_response/2.

content_length selects how the encoder frames a response:

  • auto (the default) adds Content-Length when the header list carries neither content-length nor transfer-encoding, and the status permits the field.
  • omit suppresses the automatic field at any status. A server that answers a CONNECT request with a 2xx status uses it, because RFC 9110 Section 8.6 forbids the field there and the response map carries no request method.

prepared carries a field block that prepare_headers/1 validated once. The encoder writes those octets without a second scan. The derived Content-Length comes first, then the prepared block, then the field lines of this message. content_length does not apply to a request, because the derived field there follows the body length.

encode_error()

-type encode_error() ::
          {invalid_field_name, binary()} |
          {invalid_field_value, binary()} |
          {invalid_reason_phrase, binary()} |
          {invalid_request_target, binary()} |
          {forbidden_trailer_field, binary()} |
          {trailers_require_chunked, nhttp_lib:headers()}.

Reason an encoder refuses to serialise a message.

Each arm carries the offending value so that the caller can log it. The encoder never repairs the value and never strips a byte from it.

opts()

-type opts() ::
          #{max_header_size => pos_integer(),
            max_headers_count => pos_integer(),
            max_body_size => pos_integer(),
            scheme => nhttp_lib:scheme(),
            peer => nhttp_lib:peer()}.

parse_error()

-type parse_error() ::
          bad_request_line | bad_status_line | bad_header | header_too_large | too_many_headers |
          {body_too_large, Size :: non_neg_integer(), Max :: non_neg_integer()} |
          invalid_content_length | duplicate_content_length | conflicting_framing |
          unsupported_transfer_encoding | invalid_chunk_size | incomplete_chunk | invalid_method |
          invalid_version | unexpected_eof |
          {protocol_error, term()}.

parse_result(T)

-type parse_result(T) ::
          {ok, T, BytesConsumed :: pos_integer()} |
          {more, MinBytes :: pos_integer()} |
          {error, parse_error()}.

prepared()

-opaque prepared()

A field block that prepare_headers/1 validated once.

The value holds the field lines as one binary and the answer to the framing question: whether one of those lines is Content-Length or Transfer-Encoding.

req()

-type req() :: nhttp_lib:request().

resp()

-type resp() :: nhttp_lib:response().

version()

-type version() :: http1_0 | http1_1.

Functions

body_stream_from_response/3

-spec body_stream_from_response(nhttp_lib:method(), nhttp_lib:status(), nhttp_lib:headers()) ->
                                   body_stream().

Compute the response body framing mode from the request method, response status, and response headers (RFC 9112 §6.3). Pure helper: pass the values parsed by parse_response_headers/1,2 plus the method of the matching request. The returned body_stream() is fed into parse_response_body/2. Framing rules in order:

  • HEAD request → none (HEAD responses never have a body).
  • 1xx, 204, 304 status → none.
  • Transfer-Encoding with chunked as the single final coding → {chunked, _}. Every Transfer-Encoding field line contributes to the coding list, in order of receipt (RFC 9110 §5.3).
  • Transfer-Encoding with any other coding list → until_close (RFC 9112 §6.3 #4).
  • Content-Length: N{length, N}.
  • otherwise → until_close (RFC 9112 §6.3 #7). The chunked / length walkers do not enforce header or body size limits in this entry point. Callers reading from untrusted peers should validate sizes at the recv site or wrap the stream.

encode_chunk/1

-spec encode_chunk(iodata()) -> iolist().

Encode a chunk for chunked transfer encoding.

encode_last_chunk()

-spec encode_last_chunk() -> binary().

Encode the final (zero-length) chunk.

encode_request(Req)

-spec encode_request(req()) -> {ok, iolist()} | {error, encode_error()}.

Encode an HTTP/1.1 request to iolist. The encoder refuses a message that cannot be framed unambiguously on the wire. It returns {error, {invalid_request_target, Target}} for a target that is empty or that carries a byte at or below 0x20 or the byte 0x7F, {error, {invalid_field_name, Name}} for a field name that is not a token (RFC 9110 Section 5.6.2), and {error, {invalid_field_value, Value}} for a field value that carries CR, LF, NUL, another control byte, or 0x7F (RFC 9110 Section 5.5). RFC 9112 Section 2.2 forbids a sender to generate a bare CR in any protocol element other than the content, and Section 11.1 names this filtering as the mitigation for request smuggling and response splitting. No value is repaired and no byte is stripped. The message is refused whole.

encode_request/2

-spec encode_request(req(), enc_opts()) -> {ok, iolist()} | {error, encode_error()}.

Encode an HTTP/1.1 request to iolist under the given encoder options. See encode_request/1 for the framing rules and the rejected byte classes. The request path reads the prepared key of enc_opts/0 only. A request carries a derived Content-Length when its body is not empty, so content_length has no meaning here.

{ok, Static} = nhttp_h1:prepare_headers([{<<"User-Agent">>, <<"acme/1.0">>}]),
{ok, Io} = nhttp_h1:encode_request(Req, #{prepared => Static}).

encode_response(Resp)

-spec encode_response(resp()) -> {ok, iolist()} | {error, encode_error()}.

Encode an HTTP/1.1 response to iolist. Equivalent to encode_response(Resp, #{}). The encoder adds Content-Length when the header list carries neither content-length nor transfer-encoding, including a Content-Length: 0 on an empty body. The encoder adds no Content-Length at a 1xx, 204, or 304 status. RFC 9110 Section 8.6 forbids the field at 1xx and 204. It permits the field at 304 only at the length that a 200 response would have carried, which this encoder cannot compute, so a caller that knows the value supplies it in the header list. A 2xx response to a CONNECT request also carries no Content-Length. The response map holds no request method, so that case needs encode_response/2 with #{content_length => omit}. The encoder refuses a message that cannot be framed unambiguously on the wire. It returns {error, {invalid_reason_phrase, Reason}} for a reason phrase outside 1*( HTAB / SP / VCHAR / obs-text ) (RFC 9112 Section 4.1), {error, {invalid_field_name, Name}} for a field name that is not a token (RFC 9110 Section 5.6.2), and {error, {invalid_field_value, Value}} for a field value that carries CR, LF, NUL, another control byte, or 0x7F (RFC 9110 Section 5.5). An empty reason phrase stays legal, because the status-line grammar makes the element optional. RFC 9112 Section 2.2 forbids a sender to generate a bare CR in any protocol element other than the content, and Section 11.1 names this filtering as the mitigation for response splitting. No value is repaired and no byte is stripped. The message is refused whole.

encode_response/2

-spec encode_response(resp(), enc_opts()) -> {ok, iolist()} | {error, encode_error()}.

Encode an HTTP/1.1 response to iolist under the given encoder options. See encode_response/1 for the framing rules, the rejected byte classes, and enc_opts/0 for the options. A trailers key that holds a non-empty field list frames the body with the chunked transfer coding: the header block, one chunk that carries the whole body, then the last chunk, the trailer section and the closing CRLF. The header list must already carry a Transfer-Encoding whose final coding is chunked, because RFC 9110 Section 6.5.1 makes a trailer section possible only when an explicit framing mechanism enables it, and RFC 9112 Section 7.1.2 names the chunked transfer coding as that mechanism for HTTP/1.1. A response framed any other way holds no position for the field lines, so the encoder returns {error, {trailers_require_chunked, Trailers}} rather than drop them. encode_trailers/1 states which trailer field names the encoder refuses. A prepared block travels ahead of the field lines of this message. A response that carries a trailer section keeps its Transfer-Encoding in the message header list, because require_chunked_framing/2 reads that list and a prepared block records only that a framing field is present, never which transfer coding is final.

encode_response_head(Version, Status, Headers)

-spec encode_response_head(version(), nhttp_lib:status(), nhttp_lib:headers()) ->
                              {ok, iolist()} | {error, encode_error()}.

Encode HTTP/1.x response headers for streaming. Used when sending chunked responses - sends status line + headers only. The reason phrase comes from the status code, so only the header list is subject to validation. See encode_response/1 for the rejected byte classes.

encode_response_head(Version, Status, Headers, EncOpts)

-spec encode_response_head(version(), nhttp_lib:status(), nhttp_lib:headers(), enc_opts()) ->
                              {ok, iolist()} | {error, encode_error()}.

Encode HTTP/1.x response headers for streaming, under the given encoder options. The head path reads the prepared key of enc_opts/0 only. A streaming response derives no Content-Length, so content_length has no meaning here. The prepared block travels ahead of the field lines of this message.

encode_trailers/1

-spec encode_trailers(nhttp_lib:headers()) -> {ok, iolist()} | {error, encode_error()}.

Encode the terminating sequence of a chunked message, with a trailer section. The return carries the last chunk, the trailer field lines and the single CRLF that ends chunked-body (RFC 9112 Section 7.1: chunked-body = *chunk last-chunk trailer-section CRLF). A caller emits it in place of encode_last_chunk/0, and encode_trailers([]) writes exactly what encode_last_chunk/0 writes. A trailer field is validated as a header field is. The encoder returns {error, {invalid_field_name, Name}} for a name that is not a token (RFC 9110 Section 5.6.2) and {error, {invalid_field_value, Value}} for a value that carries CR, LF, NUL, another control byte, or 0x7F (RFC 9110 Section 5.5). The encoder also refuses the names transfer-encoding, content-length, host and trailer with {error, {forbidden_trailer_field, Name}}. RFC 9112 Section 7.1.3 has a recipient compute the content length and rewrite Transfer-Encoding at the point where the trailer section arrives, so a recipient that merges one of those fields into the header section holds two contradictory framing statements for one message. RFC 9112 Section 11.1 names that filtering as the mitigation for request smuggling and response splitting. The four names are a defence against a recipient that merges in breach of RFC 9110 Section 6.5.1. They are not an RFC enumeration, and the list does not grow. RFC 9110 Section 6.5.1 puts the general rule on the sender: generate a trailer field only when the definition of that field name permits trailer use. No registry records that permission, so the encoder cannot check it and the caller owns it. RFC 9110 Section 6.6.2 asks a sender that intends to write a trailer section to announce the names in a Trailer header field.

finalize_response_body/1

-spec finalize_response_body(body_stream()) -> {ok, [body_chunk()]} | {error, parse_error()}.

Signal end-of-stream for a response body parse driven by parse_response_body/2. Used to terminate until_close framing when the underlying transport closes, and to surface mid-body framing errors for {length, _} and {chunked, _}.

  • none, {length, 0}, or until_close{ok, [{fin, []}]}.
  • {length, N>0}{error, unexpected_eof}.
  • {chunked, _} mid-body → {error, unexpected_eof}.

parse_request/1

-spec parse_request(binary()) -> parse_result(req()).

Parse an HTTP/1.1 request from binary. Returns {ok, Request, BytesConsumed} on success. Use split_at/2 to get the remaining buffer.

parse_request/2

-spec parse_request(binary(), opts()) -> parse_result(req()).

Parse HTTP/1.1 request with options. Options can include: max_header_size, max_headers_count, max_body_size.

parse_request_body/2

-spec parse_request_body(binary(), body_stream()) ->
                            {ok, [body_chunk()], body_stream(), non_neg_integer()} |
                            {more, pos_integer(), body_stream()} |
                            {error, parse_error()}.

Feed body bytes for a streaming request whose headers were parsed via parse_request_headers/1,2. Returns one of:

  • {ok, Chunks, NewStream, BytesConsumed}: emits zero or more body_chunk() events. A {fin, Trailers} chunk signals the body is fully consumed; subsequent calls on the returned stream are not needed.
  • {more, MinBytes, NewStream}: not enough buffer to make progress. The caller should buffer at least MinBytes more bytes and call again with the same NewStream.
  • {error, parse_error()}: framing error (bad chunk size, body too large, header limits exceeded in trailers).

parse_request_headers/1

-spec parse_request_headers(binary()) ->
                               {ok, req(), body_stream(), non_neg_integer()} |
                               {more, pos_integer()} |
                               {error, parse_error()}.

Warning

This zero-arg variant enforces no size or count limits on the input and is unsafe to use against untrusted peers. Production callers reading from the network MUST use parse_request_headers/2 and pass max_header_size, max_headers_count, and max_body_size (see opts/0). Parse HTTP/1.1 request headers only, without consuming the body. Returns {ok, Request, BodyStream, BytesConsumed}. The returned request map has body => streaming; the body bytes (if any) are read separately via parse_request_body/2.

parse_request_headers/2

-spec parse_request_headers(binary(), opts()) ->
                               {ok, req(), body_stream(), non_neg_integer()} |
                               {more, pos_integer()} |
                               {error, parse_error()}.

Parse HTTP/1.1 request headers only, without consuming the body, enforcing the supplied limits. Returns {ok, Request, BodyStream, BytesConsumed}. The body framing mode is encoded in BodyStream:

  • none: no body (no Content-Length, no Transfer-Encoding, or Content-Length: 0).
  • {length, N}: N body bytes remain to be read.
  • {chunked, _}: chunked transfer encoding; opaque state to feed back into parse_request_body/2. The returned request map carries body => streaming instead of buffered bytes. Use parse_request_body/2 to drive the body stream. Every Transfer-Encoding field line contributes to one coding list, in order of receipt (RFC 9110 §5.3). The parser returns {error, unsupported_transfer_encoding} unless that list holds chunked exactly once, as the final coding (RFC 9112 §6.1 and §6.3 #4).

parse_response/1

-spec parse_response(binary()) -> parse_result(resp()).

Parse an HTTP/1.1 response from binary. Returns {ok, Response, BytesConsumed} on success. Use split_at/2 to get the remaining buffer.

parse_response/2

-spec parse_response(binary(), opts()) -> parse_result(resp()).

Parse HTTP/1.1 response with options. Options can include: max_header_size, max_headers_count, max_body_size.

parse_response_body/2

-spec parse_response_body(binary(), body_stream()) ->
                             {ok, [body_chunk()], body_stream(), non_neg_integer()} |
                             {more, pos_integer(), body_stream()} |
                             {error, parse_error()}.

Feed body bytes for a streaming response whose headers were parsed via parse_response_headers/1,2 and whose framing mode was selected via body_stream_from_response/3. For none and {length, 0}, returns {ok, [{fin, []}], none, 0}. For {length, N>0} and {chunked, _}, behaves identically to parse_request_body/2. For until_close, emits [{data, _}] for whatever bytes are in the buffer and keeps the stream open. The caller must signal EOF via finalize_response_body/1 when the underlying transport closes.

parse_response_head/1

-spec parse_response_head(binary()) ->
                             {ok,
                              nhttp_lib:status(),
                              binary(),
                              version(),
                              nhttp_lib:headers(),
                              binary()} |
                             {more, pos_integer()} |
                             {error, parse_error()}.

Equivalent to parse_response_head/2.

parse_response_head/2

-spec parse_response_head(binary(), opts()) ->
                             {ok,
                              nhttp_lib:status(),
                              binary(),
                              version(),
                              nhttp_lib:headers(),
                              binary()} |
                             {more, pos_integer()} |
                             {error, parse_error()}.

Parse HTTP/1.1 response headers only, like parse_response_headers/2, but also return the reason phrase and protocol version from the status line. Returns {ok, Status, Reason, Version, Headers, Rest} where Rest is the binary after the headers. Used by streaming callers that must preserve the full response head (status, reason, version) while reading the body separately via parse_response_body/2.

parse_response_headers/1

-spec parse_response_headers(binary()) ->
                                {ok, nhttp_lib:status(), nhttp_lib:headers(), binary()} |
                                {more, pos_integer()} |
                                {error, parse_error()}.

Warning

This zero-arg variant enforces no size or count limits on the input and is unsafe to use against untrusted peers. A malicious response can exhaust memory by sending arbitrarily many or arbitrarily large header fields. Production callers reading from the network MUST use parse_response_headers/2 and pass max_header_size and max_headers_count (see opts/0). Parse HTTP/1.1 response headers only, without body. Returns {ok, Status, Headers, Rest} where Rest is the binary after headers. Used for streaming responses where the body is read separately.

parse_response_headers/2

-spec parse_response_headers(binary(), opts()) ->
                                {ok, nhttp_lib:status(), nhttp_lib:headers(), binary()} |
                                {more, pos_integer()} |
                                {error, parse_error()}.

Parse HTTP/1.1 response headers only, without body, enforcing the supplied limits (max_header_size, max_headers_count).

prepare_headers(Headers)

-spec prepare_headers(nhttp_lib:headers()) -> {ok, prepared()} | {error, encode_error()}.

Validate a field list once and return the field lines as one binary. The encoders read every octet of every field name and every field value on every call, because RFC 9112 Section 11.1 names that filtering as the mitigation for request smuggling and response splitting. A caller that sends the same field lines on many messages pays for the same octets on every message. prepare_headers/1 moves that cost to one call.

{ok, Static} = nhttp_h1:prepare_headers([
    {<<"Server">>, <<"acme/1.0">>},
    {<<"Cache-Control">>, <<"no-store">>}
]),
{ok, Io} = nhttp_h1:encode_response(Resp, #{prepared => Static}).

The value is opaque and this function is the only path that builds one. A caller that fabricates the record writes octets that no validator read, and the library cannot prevent that any more than it can prevent a call to a private function. prepare_headers/1 refuses exactly what the encoders refuse, with the same values: {error, {invalid_field_name, Name}} for a name that is not a token (RFC 9110 Section 5.6.2) and {error, {invalid_field_value, Value}} for a value that carries CR, LF, NUL, another control byte, or 0x7F (RFC 9110 Section 5.5). The block is for field lines that many messages reuse. iolist_to_binary/1 copies them once here, so a caller that prepares a block for a single message pays more than a caller that prepares nothing. The encoder does not compare a caller supplied Content-Length against the body length, and a prepared block does not change that.

split_at/2

-spec split_at(binary(), non_neg_integer()) -> binary().

Split buffer at position, returning the remainder.