Skip to content

fewensa/rttp

Repository files navigation

rttp

A small Rust HTTP workspace with public client (rttp_client) and server (rttp-server) crates. The rttp crate remains a compatibility facade for their established APIs, while the private, unpublished rttp_test_support crate contains shared test fixtures and local socket helpers.

Client

rttp_client supports plain HTTP by default. Optional features add async request APIs and TLS implementations:

name comment
async Async request APIs
http2 Bounded prior-knowledge h2c over direct socket2 TCP connections
tls-native HTTPS with native-tls
tls-rustls HTTPS with rustls
[dependencies]
rttp_client = "0.2"

Direct TCP client connections are opened with socket2. SOCKS proxy handshakes are still delegated to the socks crate. HTTP/1.x chunked responses are decoded by the client, and response trailers are available through Response::trailers, Response::trailer, and Response::trailer_value.

Bounded HTTP/1.1 byte ranges

HttpClient can emit single bytes ranges with bounded helpers: range(start, end) writes Range: bytes=start-end, range_from(start) writes Range: bytes=start-, and range_suffix(length) writes Range: bytes=-length. The helpers reject inverted closed ranges and a zero suffix before opening a socket. Callers can still set a manual Range header with the generic header API for cases outside those helpers.

206 Partial Content and 416 Range Not Satisfiable are visible through Response::is_partial_content() and Response::is_range_not_satisfiable(). Response::content_range() parses Content-Range into ContentRange, using start and end for satisfiable ranges such as bytes 10-19/200, and no start or end for unsatisfied ranges such as bytes */200.

If-Range is available through bounded request helpers that compose with the range helpers: if_range_etag(etag) writes a single strong entity-tag validator, and if_range_date(http_date) writes an HTTP-date validator. The ETag helper rejects weak tags, *, lists, and malformed tag syntax before a socket is opened; the date helper requires a value that parses as an HTTP-date. Manual If-Range headers remain available through the generic header API for cases outside the helper validation.

Response::accept_ranges() parses response Accept-Ranges fields into AcceptRanges metadata. It returns Ok(None) when the header is absent; present values expose units(), is_none(), and accepts_bytes(). Parsing is bounded to 64 KiB per header field and 256 range units, rejects malformed or empty values, rejects duplicate units case-insensitively across all parsed fields, and treats none as an exclusive sentinel. Raw Accept-Ranges fields remain available through the ordinary response header accessors even when the typed parser rejects a malformed value.

On the client side, these APIs only emit request metadata and expose response metadata. RTTP does not generate Range requests from Accept-Ranges, evaluate If-Range, automatically retry or replay a failed or full-response range request, store cached responses, synthesize multipart range requests, implement a partial response engine, serve bytes, slice content, resume downloads, follow redirects because of range metadata, choose status-policy behavior, or apply automatic cache validation policy.

Bounded HTTP/1.1 conditional requests

HttpClient can emit common conditional validators with if_none_match(etag), if_match(etag), if_modified_since(http_date), and if_unmodified_since(http_date). The ETag helpers accept one validator per call: *, a strong tag such as "abc", or a weak tag such as W/"abc". Comma-separated validator lists remain available through the generic header API. The date helpers require values that parse as HTTP-date values before a socket is opened.

Responses expose conditional metadata with Response::is_not_modified() for 304 Not Modified, Response::is_precondition_failed() for 412 Precondition Failed, Response::etag(), and Response::last_modified(). 304 responses are handled as bodyless even if misleading body framing is present; 412 remains an ordinary response status for the caller to handle.

These helpers do not add cache storage, automatic revalidation, or a cache-control engine. If-Range is range-scoped and uses the dedicated if_range_etag and if_range_date helpers above. RTTP only emits bounded request headers and exposes response metadata; cache persistence and validation policy remain application-owned.

Bounded HTTP/1.1 informational responses and Early Hints

rttp_client skips HTTP/1.1 informational response heads before the terminal response, while preserving the skipped metadata on Response::informational_responses(). Each InformationalResponse exposes its code(), reason(), raw headers(), headers_of_name(), header_value(), and header_values(), so callers can observe 100 Continue, 102 Processing, 103 Early Hints, and other skippable 1xx heads without turning them into the final response.

The skipped head parser is bounded and validation-oriented. Each informational head is limited to the normal response-head bound, must be an HTTP/1.1 1xx status line, must contain well-formed header fields, and must not declare Content-Length or Transfer-Encoding body framing. Malformed or oversized informational heads reject the response before the final response bytes are consumed. 101 Switching Protocols is not treated as skipped metadata: it remains the terminal response for upgrade and tunnel handoff paths, and its socket handoff stays separate from Response::informational_responses().

On the server side, HttpResponse::early_hints(links) and HttpResponse::early_hints_with_headers(links, metadata) construct a bodyless 103 Early Hints response model. The helpers require at least one validated Link value, bound each header value to 64 KiB, reject malformed field bytes, and reject metadata fields that would affect connection or body framing such as Connection, Content-Length, TE, Trailer, Transfer-Encoding, and Upgrade. Manual raw headers on ordinary responses remain preserved until a typed helper is requested or a typed constructor replaces them.

Early Hints support is metadata-only. RTTP does not automatically preload linked resources, apply cache policy, redirect, retry, replay requests, generate routes, expose a streaming early-write API, or add TLS/ALPN behavior from 103 metadata.

Bounded HTTP/1.1 Cache-Control behavior

Response::cache_control() parses one or more response Cache-Control header fields into CacheControl. It exposes the common response directives no-cache, no-store, max-age, s-maxage, private, public, must-revalidate, proxy-revalidate, immutable, stale-while-revalidate, and stale-if-error. Quoted field-name lists on no-cache and private are split into field names, and unknown extension directives are preserved as CacheControlExtension values with their token name and optional parsed value.

The parser is bounded and validation-oriented. Each header field value is limited to 64 KiB, the parsed header set is limited to 256 directives, directive names and unquoted values must be valid HTTP tokens, quoted strings must be well formed, and delta-seconds values must be unquoted non-negative decimal integers that fit in u64. A malformed Cache-Control value makes Response::cache_control() return an error; the original response headers and body remain available through the ordinary response APIs.

Cache-Control parsing is intentionally separate from conditional validator helpers. Response::etag() and Response::last_modified() expose validators, and request helpers such as if_none_match() and if_modified_since() can emit conditional requests when the application chooses to do so. RTTP does not combine Cache-Control directives with validators to decide freshness, build a cache entry, or issue a follow-up request.

The client has no cache store and does not perform automatic revalidation, freshness calculation against wall-clock time, Vary matching, shared-cache policy enforcement, or automatic conditional requests. Directives such as max-age, s-maxage, no-cache, must-revalidate, and extension directives are exposed as parsed metadata only.

Bounded HTTP/1.1 Age and Expires behavior

Response::age() parses the response Age header as HTTP/1.1 delta-seconds metadata. The helper returns Ok(None) when the header is absent, returns the non-negative decimal value as u64 when it is present and valid, and returns an error for empty, signed, fractional, non-numeric, comma-list, or overflowing values. The accepted bound is exactly the u64 delta-seconds range: 0 through u64::MAX.

Response::expires() parses the response Expires header as an HTTP-date using the same HTTP-date parser used by the client date helpers. It returns Ok(None) when the header is absent, returns SystemTime for valid HTTP-date values including the standard IMF-fixdate and obsolete HTTP-date forms accepted by the parser, and returns an error for malformed or non-date values.

Malformed helper values do not reject the raw response. The original Age and Expires fields remain available through header_value, header_values, and the other raw header accessors. These helpers expose metadata only; RTTP does not calculate freshness, validate cache state against wall-clock time, store responses, match stored responses, revalidate responses, apply shared-cache policy, or issue automatic conditional requests.

Bounded HTTP/1.1 Retry-After behavior

Response::retry_after() parses a single response Retry-After header as either HTTP-date metadata or non-negative delta-seconds. It returns Ok(None) when the header is absent. Present values are exposed as RetryAfter, with delta_seconds() returning u64 for the delta form and http_date() returning SystemTime for the date form.

The helper is bounded and validation-oriented. The header value is limited to 64 KiB, duplicate Retry-After header fields are rejected, delta-seconds must be unsigned decimal digits that fit in u64, and malformed dates or other invalid values return an error. The original response headers remain available through Response::header_value() and Response::header_values().

RTTP does not sleep, retry, replay requests, apply backoff, integrate with a scheduler, calculate cache freshness, or decide status-code retry policy from Retry-After.

Bounded HTTP/1.1 Allow behavior

Response::allow() parses one or more response Allow header fields into Allow metadata. It returns Ok(None) when the header is absent. Present values are parsed as comma-separated HTTP method tokens across all Allow fields in wire order, and the resulting method list is exposed by Allow::methods() and checked with Allow::contains_method(method).

The helper is bounded and validation-oriented. Each header field value is limited to 64 KiB, the parsed method list is limited to 256 entries, and each method must be a valid HTTP token. Empty list members, malformed method tokens, duplicate method names, oversized values, and too many methods make Response::allow() return an error while leaving the original response headers and body available through the ordinary response APIs.

The helper is metadata-only. rttp_client does not choose fallback methods, retry or replay requests, or attach automatic behavior to 405 or OPTIONS responses based on Allow.

Bounded HTTP/1.1 Content-Language behavior

Response::content_language() parses one or more response Content-Language header fields into ContentLanguage metadata. It returns Ok(None) when the header is absent. Present values are parsed as comma-separated language ranges across all Content-Language fields in wire order, and the resulting list is exposed by ContentLanguage::tags(). ContentLanguage::parse(value) is available when callers want to validate a single raw field value directly.

The helper is bounded and validation-oriented. Each header field value is limited to 64 KiB, the parsed tag list is limited to 256 entries, and concrete tags must contain non-empty ASCII alphanumeric subtags separated by hyphens with an alphabetic primary subtag. Empty members, malformed values, duplicate tags across one or more helper-parsed header fields, oversized values, and too many tags make Response::content_language() return an error while leaving the original response headers and body available through the ordinary response APIs.

The helper interoperates with the existing response metadata helpers by preserving raw headers and parsing only when requested. RTTP does not perform automatic language negotiation, locale fallback, variant matching, cache policy, retry, replay, redirect, or status-policy behavior from Content-Language.

Bounded HTTP/1.1 Content-Location behavior

Response::content_location() parses a response Content-Location header into ContentLocation metadata. It returns Ok(None) when the header is absent and rejects duplicate header fields because Content-Location is handled as a singleton response metadata field. ContentLocation::parse(value) is available when callers want to validate one raw field value directly; it trims outer HTTP optional whitespace and exposes the validated value with ContentLocation::as_str().

The helper is bounded and validation-oriented. The field value is limited to 64 KiB and must be a non-empty absolute URI or relative URI reference that can be parsed without control characters or unsafe field-value characters. Malformed values, duplicated singleton fields, and oversized values make Response::content_location() return an error while leaving the original response headers and body available through Response::header_value(), Response::header_values(), and the other response metadata helpers.

The helper interoperates with adjacent response metadata helpers such as Response::cache_control(), Response::allow(), Response::content_language(), Response::vary(), Response::retry_after(), Response::age(), Response::expires(), and Response::accept_ranges() by preserving raw headers and parsing only when requested. It is metadata-only: RTTP does not treat Content-Location as redirect behavior, cache variant selection, representation replacement, retry/replay behavior, route generation, or status-policy behavior.

Bounded HTTP/1.1 representation metadata behavior

Response::content_type() parses a singleton response Content-Type header into ContentType metadata. It returns Ok(None) when the header is absent and rejects duplicate Content-Type fields. Present values expose the normalized media type through type_(), subtype(), essence(), and is(type, subtype), plus ordered parameters through parameters() and case-insensitive parameter(name). ContentType::parse(value) is available when callers want to validate one raw field value directly.

Response::content_encoding() parses one or more response Content-Encoding fields into ContentEncoding metadata. It returns Ok(None) when the header is absent. Present values are parsed as comma-separated content codings across all fields in wire order and exposed through ContentEncoding::codings(). Coding spelling and order are preserved, while duplicate detection is case-insensitive.

Both helpers are bounded and validation-oriented. Each field value is limited to 64 KiB. Client Content-Type parsing accepts at most 256 parameters, lowers the media type and parameter names, rejects missing or malformed media types, malformed parameter syntax, malformed quoted strings, duplicate parameters, duplicate singleton fields, CR/LF injection, oversized values, and too many parameters. Client Content-Encoding parsing accepts at most 256 codings and rejects empty members, malformed tokens, duplicate codings, oversized values, and too many codings. Parse errors do not reject the raw response: original headers and body remain available through Response::header_value(), Response::header_values(), and the ordinary body APIs.

let content_type = response.content_type()?.expect("Content-Type");
if content_type.is("application", "json") {
  let charset = content_type.parameter("charset");
}

let content_encoding = response.content_encoding()?.expect("Content-Encoding");
assert_eq!(vec!["gzip", "br"], content_encoding.codings());

These helpers are representation metadata only. rttp_client does not perform MIME sniffing, body decoding from this metadata, charset transcoding, compression or decompression policy, negotiation, cache policy, redirects, retry/replay, or filesystem serving from Content-Type or Content-Encoding.

Bounded HTTP/1.1 Vary behavior

Response::vary() parses one or more response Vary header fields into Vary metadata. A parsed value is either the wildcard form, exposed by Vary::is_any(), or a bounded list of normalized field names exposed by Vary::field_names() and checked with Vary::contains_field_name(name). Field-name comparison is case-insensitive, and parsed field names are deduplicated in lowercase form.

Vary: * is treated as a distinct wildcard result: it means the response cannot be selected by comparing a bounded set of request header fields. The wildcard form cannot be combined with named fields. Each Vary field value is limited to 64 KiB, the parsed field-name list is limited to 256 entries, and each named member must be a valid HTTP token. Empty members, malformed field names, mixed wildcard/named values, oversized values, and too many field names make Response::vary() return an error while leaving the original response headers and body available through the ordinary response APIs.

The helper is metadata-only. RTTP does not store cache entries, match stored responses, persist cache keys, replay requests, enforce shared-cache policy, or issue automatic conditional requests based on Vary.

Bounded trailer behavior

Trailer support is explicit and bounded by protocol path. On the client, HttpClient::trailer configures request trailer fields. Those fields are sent for HTTP/1.1 only by emit_streaming_chunked; fixed-length HTTP/1.1 requests and buffered emit requests do not have an HTTP/1.1 trailer section. With the http2 feature enabled, the same configured request trailers are sent as HTTP/2 trailing HEADERS by both emit_http2_prior_knowledge and the explicit emit_http2_upgrade h2c path after request DATA for buffered POST, PUT, and PATCH requests. The bounded h2c client rejects request trailers for http2_extended_connect, and the bodyless GET, HEAD, DELETE, OPTIONS, and TRACE paths cannot carry request DATA before trailers.

Response trailers are read through the existing Response trailer accessors. For HTTP/1.1, RTTP exposes only trailers that arrive in a chunked response after the terminating zero-size chunk. For bounded h2c, peer response trailers arrive as trailing HEADERS on the active stream and are exposed through the same accessors. In both request and response directions, trailer names must be ordinary field names: HTTP/2 pseudo-headers and fields reserved for connection state, routing, authentication/cookies, transfer framing, or payload framing are rejected instead of passed to application code.

HTTP/2 trailer support does not make the generic HTTP/1.1 upgrade() or CONNECT handoff paths parse trailers. The h2c Upgrade client path is opt-in through emit_http2_upgrade and replaces the initial HTTP/1.1 exchange with the bounded HTTP/2 stream model after 101 Switching Protocols; non-h2c Upgrade handoffs remain caller-owned bytes.

Bounded HTTP/2 CONTINUATION behavior

With the http2 feature enabled, RTTP supports large HTTP/2 header blocks by splitting outbound HEADERS or trailing HEADERS into an initial HEADERS frame followed by CONTINUATION frames when the encoded HPACK block is larger than the active peer SETTINGS_MAX_FRAME_SIZE. The same bounded decoder reassembles inbound HEADERS plus CONTINUATION fragments before normal HPACK decoding and header-list validation. This applies to request headers, response headers, and trailing HEADERS on the bounded h2c paths.

Frame-size settings remain frame limits, not metadata-size limits. A legal peer SETTINGS_MAX_FRAME_SIZE value from 16,384 through 16,777,215 bytes controls how RTTP fragments outbound header blocks, DATA, and trailing HEADERS. Inbound frames larger than the active local frame-size limit are rejected even if their decoded metadata would otherwise be acceptable. Decoded metadata is still bounded separately by SETTINGS_MAX_HEADER_LIST_SIZE and by the HPACK dynamic table limits documented for the client and server paths.

CONTINUATION ordering is strict. Once a HEADERS frame starts a header block without END_HEADERS, only CONTINUATION frames for that same stream may appear until END_HEADERS closes the block. RTTP rejects orphan CONTINUATION frames, CONTINUATION on stream 0, CONTINUATION on the wrong stream, interleaved DATA or control frames before END_HEADERS, and EOF before a pending header block is closed. Rejected header-block ordering failures happen before handler dispatch or before a client response is returned.

The behavior is the same h2c stream model after both entry points: emit_http2_prior_knowledge and explicit emit_http2_upgrade on the client, and HTTP/2 prior-knowledge preface detection or valid Upgrade: h2c on the server. Generic HTTP/1.1 Upgrade, CONNECT, proxy, TLS ALPN, server push, extension callback, persistent session, and unbounded multiplexing paths do not gain additional HTTP/2 header-block handling.

Tested client protocol coverage

area tested coverage limits
HTTP/1.1 response parsing Content-Length, chunked transfer coding, chunk extensions, informational responses, bodyless 204/304, duplicate Set-Cookie, and framing ambiguity rejection Not a complete RFC conformance suite
HTTP/1.1 request emission Origin-form requests, absolute-form proxy requests, CONNECT, HEAD, fixed bodies, streaming chunked uploads, and Expect: 100-continue SOCKS handshakes are delegated to the socks crate
Upgrade and tunnel handoff CONNECT returns the tunnel socket after a successful 200; upgrade() returns the socket after 101 Switching Protocols and skips interim 1xx responses Upgraded protocols are handed to the caller and are not parsed by rttp_client
Redirects Auto-redirect covers 301, 302, 303, 307, and 308 method/body behavior, relative and absolute Location resolution, same- and cross-authority header handling, loop detection, and redirect bounds Redirects are HTTP client behavior, not a browser policy implementation
Byte ranges range, range_from, range_suffix, if_range_etag, and if_range_date emit bounded HTTP/1.1 range request metadata; Response::content_range, accept_ranges, is_partial_content, and is_range_not_satisfiable expose Content-Range, Accept-Ranges, 206, and 416 metadata while preserving raw headers No Range request generation from Accept-Ranges, client-side If-Range evaluation, partial response engine, byte serving, content slicing, download resume, automatic retry/replay, cache storage, redirect handling, status-policy behavior, multipart range generation, or automatic cache validation policy
Conditional requests if_none_match, if_match, if_modified_since, and if_unmodified_since emit bounded HTTP/1.1 validators; Response::is_not_modified, is_precondition_failed, etag, and last_modified expose 304/412 metadata One ETag validator per helper call, If-Range is range-scoped, no cache storage, no automatic revalidation, and no cache-control engine
Informational responses and Early Hints Response::informational_responses exposes skipped bounded HTTP/1.1 1xx heads, including 103 Early Hints, with preserved raw headers; server HttpResponse::early_hints/early_hints_with_headers construct validated bodyless 103 metadata 101 Switching Protocols remains terminal for upgrade handoff; no automatic preload execution, cache policy, redirect/retry/replay, route generation, streaming early-write API, TLS/ALPN behavior, or status-policy behavior
Cache-Control, Age, Expires, Retry-After, and Allow Response::cache_control parses bounded response directives, numeric freshness fields, quoted field-name lists, and extension directives; Response::age parses bounded delta-seconds; Response::expires parses bounded HTTP-date metadata; Response::retry_after parses bounded delta-seconds or HTTP-date metadata; Response::allow parses bounded ordered method metadata No cache storage, automatic revalidation, wall-clock freshness calculation, Vary matching, shared-cache policy enforcement, automatic conditional requests, automatic sleep, retry, replay, backoff, scheduler integration, fallback method selection, or status-code policy engine
Content-Language Response::content_language parses bounded response Content-Language fields into ordered language metadata while preserving raw headers No automatic language negotiation, locale fallback, variant matching, cache policy, retry, replay, redirect, or status-policy behavior
Content-Location Response::content_location and ContentLocation::parse parse bounded singleton response Content-Location metadata while preserving raw headers No redirect behavior, cache variant selection, representation replacement, retry/replay, route generation, or status-policy behavior
Content-Type and Content-Encoding Response::content_type/ContentType::parse parse bounded singleton Content-Type metadata, and Response::content_encoding/ContentEncoding::parse parse bounded ordered Content-Encoding codings while preserving raw headers on parse failures No MIME sniffing, body decoding, charset transcoding, compression/decompression policy, negotiation, cache policy, redirects, retry/replay, or filesystem serving
Content-Disposition Client Response::content_disposition and server HttpContentDisposition, HttpResponse::with_content_disposition, with_attachment_filename, and content_disposition parse or declare bounded singleton response Content-Disposition metadata, preserve raw headers on parse failures, and preserve parsed filename plus filename* parameter values as metadata No automatic download, filesystem path handling, MIME sniffing, cache behavior, redirect behavior, retry/replay, negotiation, or status-policy behavior
Vary Response::vary parses bounded response Vary fields into wildcard or normalized case-insensitive field-name metadata No cache storage, stored-response matching engine, cache key persistence, automatic request replay, shared-cache policy enforcement, or automatic conditional requests
Trailers Chunked response trailers are exposed for blocking and async APIs; streaming chunked uploads can send declared request trailers Application metadata trailers such as X-Trace are allowed; pseudo-header, connection-specific, routing, authentication/cookie, and framing trailer fields are rejected
Bounded h2c client With http2, direct socket2 h2c sends GET, HEAD, bodyless DELETE, OPTIONS, or TRACE, buffered POST, PUT, or PATCH requests, and opt-in RFC 8441 extended CONNECT request HEADERS via http2_extended_connect, opens at most one request stream, supports prior-knowledge with emit_http2_prior_knowledge, supports explicit HTTP/1.1 Upgrade: h2c negotiation with emit_http2_upgrade, advertises SETTINGS_ENABLE_PUSH = 0, advertises SETTINGS_ENABLE_CONNECT_PROTOCOL = 1 only for the explicit extended CONNECT path, validates received SETTINGS_ENABLE_PUSH values as only 0 or 1, honors initial peer SETTINGS_MAX_CONCURRENT_STREAMS by failing before request HEADERS when the peer allows zero streams, honors peer-advertised SETTINGS_MAX_HEADER_LIST_SIZE request metadata limits, accepts only legal SETTINGS_MAX_FRAME_SIZE values from 16,384 through 16,777,215 bytes, splits outbound HEADERS, DATA, and trailers to the active peer frame-size limit, rejects oversized inbound frames when a configured local frame-size limit is exceeded, bounds HPACK dynamic table use with SETTINGS_HEADER_TABLE_SIZE, strips HTTP/1.x connection-specific request fields before emission, rejects connection-specific peer response fields, suppresses HEAD response bodies, treats RST_STREAM on the active stream as a bounded reset/cancellation signal, acknowledges inbound PING without ACK on stream 0 and exactly 8 octets with matching opaque data, ignores inbound PING ACK, rejects malformed PING frames, DATA bodies, trailers, HPACK static Huffman strings, bounded large header blocks, padded incoming frames, GOAWAY shutdown boundaries, PRIORITY metadata validation without scheduling, HTTP/2-allowed unknown/extension frame ignoring inside this bounded path, reserved stream-id high-bit normalization, and conservative DATA flow control Ordinary CONNECT, header-configured :protocol metadata, non-h2c HTTP/1.1 Upgrade handoff requests, and proxies are rejected deterministically, and PUSH_PROMISE/server push is rejected instead of managed; bounded direct h2c only, with no keepalive timers, no automatic client/server initiated PING policy, no public cancellation callback API, no dynamic policy API, no extension callback API, no full extension negotiation, TLS ALPN, external h2 integration, proxy tunneling to h2, proxy h2, tunnel handoff, connection pooling, persistent HTTP/2 session management, automatic retry/replay, server push, full session manager, full stream state machine, full multiplex scheduler, unbounded multiplex scheduling, general multiplexing, priority scheduling, request bodies or trailers for extended CONNECT, or request bodies for GET, HEAD, DELETE, OPTIONS, or TRACE

With the http2 feature enabled, emit_http2_prior_knowledge sends a bounded prior-knowledge h2c request over a direct socket2 TCP connection. It opens at most one request stream and honors the peer's initial SETTINGS_MAX_CONCURRENT_STREAMS: a value of zero rejects the request before HEADERS are sent. It also honors the peer's advertised SETTINGS_MAX_HEADER_LIST_SIZE for request metadata: encoded request HEADERS and trailing HEADERS must stay within that peer boundary before emission, while peers that do not advertise the setting keep the bounded direct-client default. It supports GET, HEAD, bodyless DELETE, OPTIONS, or TRACE, buffered POST, PUT, or PATCH requests, and the explicit HttpClient::http2_extended_connect(protocol) mode for bounded RFC 8441 extended CONNECT request HEADERS. Non-empty buffered request bodies are sent as DATA frames for the write methods. GET, HEAD, DELETE, OPTIONS, TRACE, and extended CONNECT requests with bodies are rejected; HEAD, bodyless DELETE, OPTIONS, TRACE, and extended CONNECT requests do not send request DATA frames, and any HEAD response DATA frames are consumed without being exposed as a response body. The client advertises SETTINGS_ENABLE_PUSH = 0 in its initial SETTINGS frame so peers see server push disabled, and it advertises SETTINGS_ENABLE_CONNECT_PROTOCOL = 1 only when http2_extended_connect(protocol) is used. It validates received SETTINGS_ENABLE_PUSH values as only 0 or 1; any other value rejects the bounded h2c handshake. emit_http2_upgrade is the explicit HTTP/1.1 h2c Upgrade variant of the same bounded single-request client path. It is opt-in and separate from emit_http2_prior_knowledge: the client first sends an HTTP/1.1 request with Connection: Upgrade, HTTP2-Settings, Upgrade: h2c, and the local SETTINGS payload in HTTP2-Settings, requires a 101 Switching Protocols response that negotiates h2c, then sends the HTTP/2 connection preface and uses the same bounded single-stream h2c request/response flow on the upgraded socket. The Upgrade variant supports the same request methods and body limits as the prior-knowledge h2c path, rejects proxies before opening a socket, rewrites any preconfigured HTTP/1.x upgrade/framing fields into the required h2c upgrade fields, and fails deterministically for invalid h2c upgrade responses. Ordinary upgrade() continues to return the socket to the caller for WebSocket-style protocols, and non-h2c HTTP/1.1 Upgrade handoff remains outside the bounded h2c client path. The client validates SETTINGS_MAX_FRAME_SIZE boundaries on both sides of the bounded h2c handshake. A configured local http2_max_frame_size is advertised only when set, must be in the legal HTTP/2 range of 16,384 through 16,777,215 bytes, and is used to reject inbound frame payloads larger than that active local limit. Peer-advertised SETTINGS_MAX_FRAME_SIZE values outside that same legal range reject the handshake. Legal peer values become the outbound frame boundary, so request HEADERS, DATA, and trailing HEADERS are split into frames no larger than the active peer limit while the client remains a single-stream prior-knowledge path. Before encoding request HEADERS, this bounded h2c client path strips HTTP/1.x connection-specific fields: Connection, Keep-Alive, Proxy-Connection, Transfer-Encoding, Upgrade, TE, Trailer, Host, and any field named by a Connection token. Peer response HEADERS are rejected when they contain Connection, Keep-Alive, Proxy-Connection, TE, Transfer-Encoding, or Upgrade. Application request trailers such as X-Trace, X-Upload-Status, or X-Upload-Checksum are valid in this bounded h2c path and are encoded as trailing HEADERS after request DATA. Configured request trailers are rejected before emission when their field name is invalid or reserved for connection/framing/routing behavior: Connection, Keep-Alive, Proxy-Connection, TE, Trailer, Transfer-Encoding, Upgrade, Content-Length, Host, Proxy-Authenticate, or Proxy-Authorization. Peer response trailers use the existing forbidden-trailer validation for invalid pseudo-header-like names, connection-specific, routing, authentication/cookie, and framing fields such as Authorization, Connection, Content-Length, Cookie, Host, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, Proxy-Connection, Set-Cookie, TE, Trailer, Transfer-Encoding, Upgrade, and WWW-Authenticate. The client supports HPACK static Huffman strings and bounded large header blocks via CONTINUATION frames. It uses HPACK dynamic entries for repeated request header and trailer fields within the peer's advertised SETTINGS_HEADER_TABLE_SIZE. The default peer limit is 4,096 bytes when the peer does not advertise the setting; a peer-advertised zero disables request dynamic indexing, so request HEADERS and trailers remain literal encoded. Peer values above 4,096 bytes are valid, but RTTP caps request dynamic indexing at its 4,096-byte bounded encoder size. Response decoding is bounded by the local advertised SETTINGS_HEADER_TABLE_SIZE: the client uses the default 4,096-byte decoder limit unless ConfigBuilder::http2_header_table_size configures and advertises another u32-sized value. Incoming HPACK dynamic table size updates from response HEADERS or trailers may shrink that decoder table, including to zero; updates above the local advertised limit are rejected. Dynamic table size updates are HPACK compression state only and do not change SETTINGS_MAX_HEADER_LIST_SIZE, trailer validation, body framing, or the single-stream h2c policy. Valid response PRIORITY frames and HEADERS priority fields are validated and ignored as metadata; malformed priority metadata is rejected, and no priority scheduling is performed. Inbound PING without ACK is acknowledged only when it arrives on stream 0 with exactly 8 octets of opaque data; the PING ACK carries that same opaque data. Inbound PING ACK is ignored for this bounded path. PING with a non-zero stream id or payload length other than 8 is malformed and rejected. RTTP does not add keepalive timers, automatic client- or server-initiated PING policy, replay behavior, a full session manager, or a full multiplex scheduler around this acknowledgement path. Unknown frame types, including extension frames, are ignored only after the prior-knowledge h2c handshake in this bounded direct-client path where HTTP/2 permits that behavior; RTTP does not expose extension callbacks or perform full extension negotiation. Reserved stream identifier high bits are masked when frames are parsed or written, which normalizes wire framing but does not add broader multiplex scheduling or persistent session management. Server push is outside this bounded client path even when a peer advertises SETTINGS_ENABLE_PUSH = 1: incoming PUSH_PROMISE frames are rejected deterministically instead of creating or tracking push state. HTTP/1.1 CONNECT tunnel handoff remains a separate client path; prior-knowledge h2c GOAWAY is treated as a bounded shutdown signal: a response already completed before GOAWAY remains usable, an active stream continues only when the peer's last-stream-id includes it, and a lower boundary rejects the response deterministically. A GOAWAY received before stream 1 is opened is treated as request refusal and no request HEADERS are sent. RTTP returns that refusal to the caller instead of retrying on a new connection; callers that know a request is safe or idempotent must choose any retry policy themselves. This protocol shutdown boundary is distinct from a transport-level disconnect, read timeout, write timeout, or TCP reset, which is reported through the normal socket/error path without an HTTP/2 last-stream-id boundary. RST_STREAM is likewise bounded to this prior-knowledge h2c client path: a reset for the active stream is reported as response cancellation, while malformed reset frames are rejected deterministically. RTTP does not expose a public cancellation callback API or retry the request automatically. Ordinary CONNECT, header-configured RFC 8441 :protocol metadata, HTTP/1.1 Upgrade handoff requests, and proxy tunneling are rejected before a client socket is opened. The explicit http2_extended_connect(protocol) mode emits :method CONNECT with :protocol, :scheme, :authority, and :path, then returns the peer's HTTP/2 response through the normal Response API; it does not hand an upgraded socket to the caller and does not send request bodies or request trailers. HTTP/1.1 CONNECT tunnel handoff and Upgrade remain separate client handoff paths; this h2c path does not provide full WebSocket-over-h2, proxy h2, TLS ALPN, tunnel handoff, persistent multiplex sessions, general tunnel scheduling, or full RFC 8441 support. Extension callback APIs, full extension negotiation, external h2 integration, connection pooling, automatic retry, server push, full stream state machines, and full HTTP/2 features such as unbounded multiplex scheduling, general multiplexing, and priority scheduling are not part of that bounded prior-knowledge client path; RTTP also does not provide a dynamic policy API for changing h2c frame-size or metadata limits at runtime.

# #[cfg(feature = "http2")]
# fn example() -> Result<(), Box<dyn std::error::Error>> {
use rttp_client::HttpClient;

let response = HttpClient::new()
  .get()
  .url("http://127.0.0.1:8080/chat")
  .http2_extended_connect("websocket")
  .emit_http2_prior_knowledge()?;
# Ok(())
# }
use rttp_client::HttpClient;

let response = HttpClient::new()
  .get()
  .url("http://127.0.0.1:8080/health")
  .emit()?;
# Ok::<(), Box<dyn std::error::Error>>(())

Server

The rttp crate exposes rttp::Http::server, which creates a blocking HttpServer listener.

[dependencies]
rttp = "0.2"
use std::time::Duration;

use rttp::server::HttpResponse;

fn main() -> std::io::Result<()> {
  let server = rttp::Http::server("127.0.0.1:0")?
    .with_read_timeout(Some(Duration::from_secs(5)))
    .with_write_timeout(Some(Duration::from_secs(5)));
  println!("listening on {}", server.local_addr()?);

  server.accept_one(|request| {
    println!("{} {}", request.method(), request.target());
    HttpResponse::ok("hello")
      .header("Transfer-Encoding", "chunked")
      .header("Trailer", "X-Trace")
      .trailer("X-Trace", "abc")
  })
}

Use HttpServer::bind directly when you already want the server type, HttpServer::local_addr to read the bound address, accept_one for one connection, and serve_requests for a fixed number of sequential connections. Use with_read_timeout and with_write_timeout to apply socket-level timeouts to each accepted connection; pass None to leave the corresponding socket timeout unset. Add Transfer-Encoding: chunked to an HttpResponse to write the complete response body with HTTP/1.x chunked transfer framing instead of an automatic Content-Length; response trailers added with HttpResponse::trailer are written after the terminating zero-size chunk. Add a Trailer response header when advertising which trailer fields will follow. The listener path uses socket2.

Bounded HTTP/1.1 byte ranges

The server provides range parsing and response constructors for applications that choose to support partial content. It does not automatically serve files or decide static-file policy. Application code should inspect Range, choose the representation and entity length, and pass the header to HttpByteRange::parse(range_header, entity_length).

Supported request forms are a single bytes=start-end, bytes=start-, or bytes=-suffix range. Open-ended ranges are clipped to the representation length, suffix ranges require a nonzero suffix, unsupported units return UnsupportedUnit, comma-separated ranges return MultipleRanges, malformed or inverted ranges return InvalidRange, and ranges beyond the entity return UnsatisfiedRange.

For a satisfiable range, HttpResponse::partial_content(body, range) returns 206 Partial Content, adds Content-Range: bytes start-end/length, and sends only the selected body bytes. For an unsatisfied range, HttpResponse::range_not_satisfiable(entity_length) returns 416 Range Not Satisfiable with Content-Range: bytes */length and an empty body.

For conditional range requests, Request::evaluate_if_range(&metadata, entity_length) composes caller-provided HttpConditionalMetadata with the existing single-range parser. Matching strong ETags or exact HTTP-date Last-Modified validators return PartialContent(HttpByteRange); non-matching, weak, invalid, or metadata-missing validators return FullResponse; guarded unsatisfied ranges return RangeNotSatisfiable. Application code still chooses the final 200, 206, or 416 response.

Multipart ranges are intentionally not generated: RTTP does not serialize multipart/byteranges or pick a multipart response for multiple requested ranges. Filesystem path normalization, MIME detection, ETag or Last-Modified generation, authorization, directory indexes, dotfile visibility, cache storage, automatic cache validation, and any automatic retry policy remain caller-owned policy before choosing 200, 206, or 416.

HttpResponse::with_accept_ranges(units) declares supported range units with one bounded comma-separated Accept-Ranges response header, while HttpResponse::with_accept_ranges_none() declares the exclusive Accept-Ranges: none sentinel. HttpResponse::accept_ranges() parses attached fields into HttpAcceptRanges, bounded to 64 KiB per field and 32 range units. Malformed or empty values, duplicate units across parsed fields, combining none with any unit, and passing none through the unit declaration helper are rejected. Manual raw Accept-Ranges headers remain preserved until a typed declaration helper replaces them or the typed parser is requested.

The server Accept-Ranges helpers are response metadata helpers that compose with HttpResponse::cache_control(), HttpResponse::vary(), HttpResponse::allow(), HttpResponse::content_language(), and the other raw header APIs. They do not parse request Range fields, generate Range requests, create a partial response engine, serve bytes, slice content, resume downloads, choose redirect or status-policy behavior, implement cache policy, retry or replay requests, or automatically select 200, 206, or 416.

Bounded HTTP/1.1 conditional requests

The server exposes conditional request primitives for applications that already know the selected representation. Use HttpConditionalMetadata with HttpEntityTag::strong("tag") or HttpEntityTag::weak("tag") and optional last_modified(SystemTime), then call Request::evaluate_conditional(&metadata) or evaluate_conditional_request(&request, &metadata).

The evaluator returns Proceed, NotModified, or PreconditionFailed. If-Match uses strong ETag comparison and takes precedence over If-Unmodified-Since; If-None-Match uses weak ETag comparison and takes precedence over If-Modified-Since. Matching If-None-Match validators return 304 Not Modified behavior for GET and HEAD, and 412 Precondition Failed behavior for other methods. HTTP-date validators are used only when they parse as HTTP-date values, and Last-Modified comparisons are made at second precision.

Use HttpResponse::not_modified(&metadata) to serialize a bodyless 304 with available ETag and Last-Modified validators. Use HttpResponse::precondition_failed() for 412; application code can still add its own headers or body when desired. A Proceed outcome means the handler should send its normal representation response.

The helper scope is intentionally bounded. RTTP does not choose ETags, read or serve files, implement static-file serving policy, store cached responses, automatically revalidate stale entries, or provide a full cache-control engine. Those remain application policy around the validator helpers.

Bounded HTTP/1.1 informational responses and Early Hints

HttpResponse::early_hints(links) constructs a bodyless 103 Early Hints response with one Link header per supplied value. HttpResponse::early_hints_with_headers(links, metadata) adds validated metadata headers alongside those links for applications that want to send adjacent response metadata before a final response. Serialize the returned model with the same response-writing path used for other HttpResponse values, then write the final response separately.

The constructors are bounded and validation-oriented. They require at least one non-empty Link value, bound each Early Hints field value to 64 KiB, reject invalid field-value bytes, reject invalid metadata field names, and keep Link values in the dedicated links argument. Metadata fields that would affect connection state or body framing are rejected, including Connection, Content-Length, Keep-Alive, Proxy-Connection, TE, Trailer, Transfer-Encoding, and Upgrade. A body assigned to the 103 model is not serialized, and Content-Length is not generated for it.

103 Early Hints is separate from 101 Switching Protocols. 101 responses remain bodyless terminal handoff responses for HttpResponse::upgrade and other caller-owned protocol transitions; they are not serialized as skipped informational metadata. Raw headers attached through ordinary HttpResponse::header calls remain preserved until a typed helper validates, parses, or replaces the relevant field.

Early Hints support is metadata-only. The server does not execute preloads, choose cache policy, redirect, retry, replay requests, generate routes, expose a streaming early-write API, alter TLS/ALPN behavior, or decide final response status from 103 metadata.

Bounded HTTP/1.1 Cache-Control behavior

Server Cache-Control helpers parse directive metadata for application policy; they do not enforce cache behavior. Request::cache_control() and HttpRequest::cache_control() parse request directives into HttpRequestCacheControl, including no-cache, no-store, max-age, max-stale with or without a value, min-fresh, no-transform, only-if-cached, and extension directives. HttpResponse::cache_control() parses response headers already attached to an HttpResponse into HttpResponseCacheControl, including no-cache, no-store, max-age, s-maxage, private, public, must-revalidate, proxy-revalidate, immutable, stale-while-revalidate, stale-if-error, quoted field-name lists, and extension directives.

Unknown extension directives are preserved rather than discarded. Each HttpCacheControlExtension exposes the directive token name and optional parsed value, with quoted-string escaping removed when a quoted value is used. The helpers do not interpret extension semantics or negotiate extension behavior.

Parsing is bounded and validation-oriented. Each Cache-Control field value is limited to 64 KiB, the parsed set is limited to 256 directives across all values passed to the helper, directive names and unquoted values must be valid HTTP tokens, quoted strings must be well formed, and delta-seconds values must be unquoted non-negative decimal integers that fit in u64. Invalid Cache-Control syntax returns HttpCacheControlParseError from the helper; it does not by itself reject the request before handler code or remove the original header from the response model.

These helpers are separate from conditional validator evaluation. Request::evaluate_conditional() and Request::evaluate_if_range() use only caller-supplied HttpConditionalMetadata and the conditional request headers. They do not consult Cache-Control directives to decide whether a response is fresh, whether it must be revalidated, or whether validators should be emitted on a later request.

RTTP does not provide cache storage, automatic revalidation, freshness calculation against wall-clock time, Vary matching, shared-cache policy enforcement, or automatic conditional requests. Directives such as max-age, s-maxage, no-cache, only-if-cached, must-revalidate, and extension directives are exposed as parsed metadata for application-owned policy.

Server Age and Expires helpers expose adjacent response metadata without adding cache policy. HttpResponse::with_age(delta_seconds) adds an Age header from a non-negative u64 delta-seconds value, and HttpResponse::age() parses an attached single Age header back into u64. The accepted bound is exactly the u64 delta-seconds range: 0 through u64::MAX. Empty, signed, fractional, non-numeric, comma-list, duplicated, and overflowing values return HttpAgeParseError.

HttpResponse::with_expires(time) serializes an HTTP-date Expires header from SystemTime, and HttpResponse::expires() parses an attached single Expires header as an HTTP-date. Valid HTTP-date values parse to SystemTime; malformed, duplicated, or non-date values return HttpExpiresParseError.

HttpResponse::with_retry_after_delta(delta_seconds) serializes a delta-seconds Retry-After header from a non-negative u64, while HttpResponse::with_retry_after_date(time) serializes an HTTP-date Retry-After header from SystemTime. HttpResponse::retry_after() parses an attached single Retry-After header into HttpRetryAfter::DeltaSeconds or HttpRetryAfter::HttpDate. Empty, signed, fractional, non-numeric non-date, comma-list, duplicated, overflowing, oversized, or malformed HTTP-date values return HttpRetryAfterParseError.

Malformed typed helper reads return validation errors, while raw HttpResponse::header("Age", ...) and HttpResponse::header("Expires", ...) and HttpResponse::header("Retry-After", ...) values remain preserved exactly as response headers. These helpers do not calculate freshness, validate cache state against wall-clock time, store responses, match stored responses, revalidate responses, enforce shared-cache policy, attach behavior to status codes, sleep, retry, replay requests, apply backoff, integrate with a scheduler, or issue automatic conditional requests.

Bounded HTTP/1.1 Allow behavior

Server-side Allow helpers expose response declaration and method-list parsing metadata without implementing method negotiation or automatic status handling. HttpResponse::with_allow(methods) validates an explicit method list and adds one comma-separated Allow header, while HttpResponse::allow() parses any Allow headers already attached to a response into HttpAllowedMethods. HttpAllowedMethods::parse(value) accepts a comma-separated list of HTTP method tokens and preserves the declared token spelling and order.

Parsing is bounded and validation-oriented. Each Allow field value is limited to 64 KiB, the parsed method list is limited to 32 entries, and each member must be a valid HTTP token. Empty members, malformed method tokens, duplicates across one or more helper-parsed header fields, oversized values, and too many methods return HttpAllowParseError from the helper. Raw HttpResponse::header("Allow", ...) values remain preserved exactly as ordinary response headers; helper parse errors do not remove existing headers.

These helpers are metadata-only. RTTP does not choose fallback methods, retry or replay requests, implement OPTIONS policy, generate 405 responses, dispatch routes, or provide a status-code policy engine from Allow.

Bounded HTTP/1.1 Content-Language behavior

Server-side Content-Language helpers expose response metadata declaration and parsing without implementing language negotiation, locale fallback, or variant matching. HttpResponse::with_content_language(languages) validates an explicit language tag list and adds one comma-separated Content-Language header, while HttpResponse::content_language() parses any Content-Language headers already attached to a response into HttpContentLanguages. HttpContentLanguages::parse(value) accepts comma-separated language tags and preserves the declared spelling and order.

Parsing is bounded and validation-oriented. Each Content-Language field value is limited to 64 KiB, the parsed language list is limited to 32 entries, and each tag must contain non-empty ASCII alphanumeric subtags separated by hyphens with an alphabetic primary subtag. Empty members, malformed tags, duplicates across one or more helper-parsed header fields, oversized values, and too many tags return HttpContentLanguageParseError from the helper. Raw HttpResponse::header("Content-Language", ...) values remain preserved exactly as ordinary response headers; helper parse errors do not remove existing headers.

These helpers interoperate with adjacent response metadata helpers such as HttpResponse::cache_control(), HttpResponse::allow(), and HttpResponse::vary() by preserving raw headers and parsing only when requested. They are metadata-only: RTTP does not perform automatic language negotiation, route selection, locale fallback, variant matching, cache policy, retry, replay, redirect, or status-policy behavior from Content-Language.

Bounded HTTP/1.1 Content-Location behavior

Server-side Content-Location helpers expose response metadata declaration and parsing without implementing redirect handling, cache selection, or route policy. HttpResponse::with_content_location(value) validates one Content-Location field value, trims outer whitespace, removes any existing raw Content-Location fields, and adds a single validated Content-Location header. HttpResponse::content_location() parses any attached Content-Location header and returns Ok(None) when the header is absent.

Parsing is bounded and validation-oriented. The field value is limited to 64 KiB, must be non-empty after trimming, and must not contain control characters. Duplicate Content-Location fields are rejected because the helper treats the header as singleton response metadata. Malformed values, duplicated singleton fields, and oversized values return HttpContentLocationParseError from the helper. Raw HttpResponse::header("Content-Location", ...) values remain preserved exactly as ordinary response headers until a typed declaration helper replaces them or the typed parser is requested.

These helpers interoperate with adjacent response metadata helpers such as HttpResponse::cache_control(), HttpResponse::allow(), HttpResponse::content_language(), HttpResponse::vary(), HttpResponse::retry_after(), HttpResponse::age(), HttpResponse::expires(), and HttpResponse::accept_ranges() by preserving raw headers and parsing only when requested. They are metadata-only: RTTP does not treat Content-Location as redirect behavior, cache variant selection, representation replacement, retry/replay behavior, route generation, or status-policy behavior.

Bounded HTTP/1.1 Content-Disposition behavior

Server-side Content-Disposition helpers expose response metadata declaration and parsing without implementing download policy, filesystem handling, MIME sniffing, cache behavior, redirect handling, retry, or negotiation. HttpContentDisposition::parse(value) validates one field value, including a token disposition type and bounded parameters. HttpContentDisposition::inline() and HttpContentDisposition::attachment() construct common dispositions, and with_parameter(name, value) adds safely serialized parameters.

HttpResponse::with_content_disposition(value) validates the provided model or field value, removes any existing raw Content-Disposition fields, and adds one validated Content-Disposition header. HttpResponse::with_attachment_filename is a convenience helper for attachment; filename=.... HttpResponse::content_disposition() parses an attached singleton header and returns Ok(None) when the header is absent.

Parsing is bounded and validation-oriented. The field value is limited to 64 KiB, parameter count is limited to 32, token positions must be valid HTTP tokens, quoted-string input must be well formed, and CR/LF or other control characters are rejected. Duplicate parameters and duplicate Content-Disposition fields are rejected by the typed parser. Raw HttpResponse::header("Content-Disposition", ...) values remain preserved exactly as ordinary response headers until a typed declaration helper replaces them or the typed parser is requested.

filename and filename* are preserved as separate parameters. The helper serializes the parameter values it is given, parses those values back as metadata, and does not decode RFC 5987 extended values or choose between filename and filename*. Applications remain responsible for any filename precedence, display, storage, or download policy. These helpers are metadata-only: RTTP does not start automatic downloads, derive filesystem paths, sniff MIME types, negotiate response variants, redirect, retry/replay, cache, or attach status-code policy from Content-Disposition.

Bounded HTTP/1.1 representation metadata behavior

Server-side representation metadata helpers expose response declaration and parsing without changing payload bytes. HttpContentType::parse(value) validates a Content-Type field, normalizes the media type and parameter names to lowercase, preserves parameter values, and exposes media_type(), parameter(name), parameters(), and header_value(). HttpContentType::new(type_name, subtype) constructs a normalized media type, and with_parameter(name, value) appends safely serialized parameters. HttpResponse::with_content_type(value) accepts any IntoHttpContentType, removes existing raw Content-Type fields, and adds one validated Content-Type header. HttpResponse::content_type() parses an attached singleton header and returns Ok(None) when the header is absent.

HttpResponseContentEncodings::parse(value) validates comma-separated Content-Encoding codings, while HttpResponseContentEncodings::from_codings(codings) validates an explicit coding list for declarations. HttpResponse::with_content_encoding(codings) removes existing raw Content-Encoding fields and adds one validated comma-separated header. HttpResponse::content_encoding() parses all attached Content-Encoding fields in wire order and returns Ok(None) when absent. Coding spelling and order are preserved, and duplicate detection is case-insensitive.

Parsing and declaration are bounded. Each Content-Type and Content-Encoding field value is limited to 64 KiB. Server Content-Type helpers accept at most 32 parameters and reject malformed media types, malformed parameter syntax, malformed quoted strings, duplicate parameters, duplicate singleton fields, CR/LF or other control bytes, oversized values, and too many parameters. Server Content-Encoding helpers accept at most 32 codings and reject empty members, malformed tokens, duplicate codings, oversized values, and too many codings. Raw HttpResponse::header(...) values remain preserved exactly as ordinary response headers until a typed declaration helper replaces them or the typed parser is requested; parser errors do not remove existing headers.

let content_type = HttpContentType::new("application", "json")?
  .with_parameter("charset", "utf-8")?;

let response = HttpResponse::ok("{}")
  .with_content_type(content_type)?
  .with_content_encoding(["gzip", "br"])?;

let codings = response.content_encoding()?.expect("Content-Encoding");
assert_eq!(vec!["gzip", "br"], codings.codings());

These helpers are metadata-only. RTTP does not perform MIME sniffing, body decoding from this metadata, charset transcoding, compression or decompression, negotiation, cache policy, redirects, retry/replay, or filesystem serving from Content-Type or Content-Encoding.

Bounded HTTP/1.1 Vary behavior

Server-side Vary helpers expose response declaration and request-selection metadata without implementing a cache. HttpResponse::with_vary(value) parses and normalizes a response Vary value before adding the header, while HttpResponse::vary() parses any Vary headers already attached to a response. HttpVary::parse(value) accepts either * or a comma-separated list of field names. Named fields are normalized to lowercase, deduplicated, and exposed through HttpVary::field_names().

Request::vary_selection(&vary) and HttpRequest::vary_selection(&vary) collect the current request header values named by a parsed HttpVary, using case-insensitive header-name matching. Wildcard Vary produces a wildcard HttpVarySelection and does not read specific request headers. Named selection values are metadata for application-owned policy; RTTP does not compare them against stored cache keys.

Parsing is bounded and validation-oriented. Each Vary field value is limited to 64 KiB, the parsed field-name list is limited to 256 entries, and each named member must be a valid HTTP token. Empty members, malformed field names, mixed wildcard/named values, oversized values, and too many field names return HttpVaryParseError from the helper. A parse error does not by itself reject a request before handler code or remove existing response headers.

These helpers do not add cache storage, a stored-response matching engine, cache key persistence, automatic request replay, shared-cache policy enforcement, or automatic conditional requests. Applications that build a cache must persist any selected request metadata and enforce their own cache policy around these helpers.

The server is intentionally small: it handles blocking HTTP/1.x request parsing for local tests and simple embedded use. It accepts fixed Content-Length and chunked request bodies, exposes chunked request trailers, applies bounded request head/body validation, handles HEAD without writing a response body, honors Connection close/keep-alive semantics across a bounded serve_requests loop, writes response body framing and response trailers consistently, and accepts Expect: 100-continue. On the same socket2 listener, the accept path detects either the HTTP/2 client preface or an HTTP/1.1 Upgrade: h2c request and dispatches the resulting h2c connection to the same minimal bounded handler, including bodyless DELETE, OPTIONS, and TRACE requests. HTTP/1.1 h2c Upgrade is opt-in on both sides: the request must be HTTP/1.1, include Connection: Upgrade, HTTP2-Settings, Upgrade: h2c, exactly one HTTP2-Settings field with a valid unpadded base64url SETTINGS payload, and no request body; malformed h2c upgrade attempts receive 400 Bad Request before handler dispatch. When the upgrade is valid, the server writes 101 Switching Protocols, consumes the client's HTTP/2 preface on the same socket, applies the advertised SETTINGS as the initial peer SETTINGS, and uses the HTTP/2 stream id sequence reserved for an HTTP/1.1 upgrade. The server advertises SETTINGS_MAX_CONCURRENT_STREAMS from the active request allowance for that bounded accept path and rejects new h2c streams once the open-stream count plus completed requests reaches that allowance. It also advertises and enforces a conservative SETTINGS_MAX_HEADER_LIST_SIZE for inbound request metadata; request HEADERS and trailing HEADERS can span CONTINUATION frames, but the decoded metadata remains bounded before handler dispatch. The server validates peer SETTINGS_ENABLE_PUSH values as only 0 or 1; any other value rejects the bounded h2c handshake. It also validates SETTINGS_ENABLE_CONNECT_PROTOCOL values as only 0 or 1; a received value of 1, whether in the initial peer SETTINGS or a later SETTINGS update, enables bounded RFC 8441 extended CONNECT request dispatch for subsequent HEADERS on that connection. Without that negotiated setting, any :protocol pseudo-header is rejected before handler dispatch. The server advertises the default 16,384-byte SETTINGS_MAX_FRAME_SIZE, rejects peer SETTINGS values outside the legal HTTP/2 range of 16,384 through 16,777,215 bytes, rejects inbound frames larger than the active local limit, and splits outbound response HEADERS, DATA, and trailing HEADERS to the active peer frame-size limit. It preserves the same HEAD body suppression for prior-knowledge h2c responses. Incoming padded HEADERS, DATA, and trailer frames are accepted without exposing padding bytes to handlers, and application trailers such as X-Trace, X-Upload-Status, and X-Upload-Checksum are preserved on Request. Trailing HEADERS that contain HTTP/2 pseudo-headers are rejected before handler dispatch. Trailer field names that affect connection state, routing, authentication/cookies, framing, or payload processing are also rejected, including Connection, Keep-Alive, Proxy-Connection, TE, Trailer, Transfer-Encoding, Upgrade, Host, Content-Length, Cache-Control, Content-Encoding, Content-Range, Content-Type, Max-Forwards, Authorization, Proxy-Authenticate, Proxy-Authorization, Cookie, Set-Cookie, and WWW-Authenticate. HPACK static Huffman strings, request dynamic table entries, and bounded large header blocks are carried with CONTINUATION frames. The server accepts peer SETTINGS_HEADER_TABLE_SIZE values as the outbound response compression allowance and applies later peer updates before encoding response trailers. If the peer advertises zero, the server evicts response dynamic entries and writes response HEADERS and trailers without dynamic indexing. Inbound request and request-trailer decoding stays bounded to the server's fixed 4,096-byte HPACK dynamic table limit; incoming dynamic table size updates may shrink that decoder table, including to zero, but updates above 4,096 bytes are rejected. These table-size boundaries affect only HPACK compression state, not decoded metadata limits, trailer validation, handler dispatch, DATA flow control, or multiplex scheduling. Prior-knowledge h2c request headers reject HTTP/1.x connection-specific fields before handler dispatch: Connection, Keep-Alive, Proxy-Connection, Transfer-Encoding, and Upgrade; TE is accepted only as te: trailers and other TE values are rejected. When serializing h2c responses, the server strips HTTP/1.x connection-specific response fields and generated HTTP/2 framing fields from HEADERS: Connection, Keep-Alive, Proxy-Connection, TE, Trailer, Transfer-Encoding, Upgrade, and Content-Length. H2c response trailers skip the existing forbidden trailer set, including invalid pseudo-header-like names, connection-specific, transfer/framing, routing, authentication, and cookie fields. Valid standalone PRIORITY frames and HEADERS priority fields are validated and ignored as metadata; malformed priority metadata is rejected, and request or response ordering does not use priority scheduling. Inbound PING without ACK is acknowledged only when it arrives on stream 0 with exactly 8 octets of opaque data; the PING ACK carries that same opaque data. Inbound PING ACK is ignored for this bounded path. PING with a non-zero stream id or payload length other than 8 is malformed and rejected. RTTP does not add keepalive timers, automatic client- or server-initiated PING policy, replay behavior, a full session manager, or a full multiplex scheduler around this acknowledgement path. Unknown frame types, including extension frames, are ignored only after the HTTP/2 preface is accepted in this bounded h2c server path where HTTP/2 permits that behavior; RTTP does not expose an extension callback API or negotiate extensions. Reserved stream identifier high bits are masked when frames are parsed or written, which normalizes frame identifiers without adding unbounded multiplexing, session management, or external h2-stack support. Server push is outside this bounded server path: inbound PUSH_PROMISE frames are rejected deterministically before handler dispatch instead of attempting push state management, and RTTP does not implement server-side push state even when a peer sends SETTINGS_ENABLE_PUSH = 1. When the bounded prior-knowledge h2c server loop finishes, it sends GOAWAY with the last completed stream id so clients have a deterministic shutdown boundary for already processed streams. If the bounded request allowance is exhausted while additional streams are already open, the server first sends a graceful GOAWAY boundary and lets streams within that boundary finish; new streams outside the boundary are refused with REFUSED_STREAM and are not dispatched to the handler. If the peer closes the TCP connection, a read/write timeout fires, or the socket is reset before GOAWAY can be written, that is transport termination rather than an HTTP/2 graceful shutdown signal and no additional stream boundary is implied. Within that same prior-knowledge h2c server path, inbound RST_STREAM is a bounded reset/cancellation signal for the affected stream: reset request streams are not dispatched to handlers, and reset response streams stop within the bounded write path. RTTP does not expose a public cancellation callback API, retry work automatically, keep persistent HTTP/2 sessions, or model a full HTTP/2 stream state machine around those resets. The h2c handler does not share the HTTP/1.1 CONNECT or non-h2c Upgrade handoff paths. Ordinary h2c CONNECT without :protocol remains unsupported proxy tunneling and is rejected before handler dispatch. Negotiated extended CONNECT is exposed to handlers as a normal Request with method CONNECT, version HTTP/2, origin-form target from :path, host derived from :authority, and Request::extended_connect_protocol() returning the :protocol value. The handler returns a normal HttpResponse; RTTP does not switch the stream to caller-owned tunnel bytes. HTTP/1.1 CONNECT authority-form requests and HttpResponse::upgrade for non-h2c protocols remain separate handoff paths for caller-owned protocols, and the h2c Upgrade detection preserves those existing handoffs when Upgrade is not h2c. TLS ALPN, extension callback APIs, full extension negotiation, external h2 integration, full WebSocket-over-h2, proxy h2, tunnel handoff, connection pooling, persistent multiplex sessions, persistent HTTP/2 session management, full RFC 8441 support, and full HTTP/2 features such as unbounded multiplexing, unbounded multiplex scheduling, general multiplexing, general tunnel scheduling, server push, and priority scheduling remain outside this bounded prior-knowledge server path. RTTP does not expose a dynamic policy API for changing the h2c frame-size or metadata limit at runtime.

use rttp::server::HttpResponse;

fn main() -> std::io::Result<()> {
  let server = rttp::Http::server("127.0.0.1:8080")?;

  server.accept_one(|request| {
    if request.method() == "CONNECT"
      && request.version() == "HTTP/2"
      && request.extended_connect_protocol() == Some("websocket")
    {
      return HttpResponse::ok("accepted extended CONNECT metadata");
    }

    HttpResponse::new(400, "Bad Request")
  })
}

It is not a full RFC-covering web server and still does not implement server TLS or async accept loops.

Tested server protocol coverage

area tested coverage limits
HTTP/1.1 request parsing Required Host validation, origin-form, absolute-form, asterisk-form OPTIONS, authority-form CONNECT, fixed and chunked bodies, chunk extensions, Expect: 100-continue, and obsolete line folding rejection Intended for local tests and simple embedded use, not full RFC coverage
HTTP/1.1 connection handling Bounded sequential serve_requests, keep-alive and close behavior for HTTP/1.1 and HTTP/1.0, pipelined request boundaries, malformed request rejection before handler dispatch Blocking listener only; no async accept loop
HTTP/1.1 response framing Automatic Content-Length, explicit chunked responses, bodyless HEAD, 101, 204, and 304, response trailers after the terminating chunk No server TLS
Byte ranges HttpByteRange parses one bytes range, Request::evaluate_if_range gates it with caller-provided strong ETag or exact HTTP-date metadata, HttpResponse::partial_content/range_not_satisfiable serialize 206/416 with Content-Range, and HttpAcceptRanges plus HttpResponse::with_accept_ranges/with_accept_ranges_none/accept_ranges declare and parse bounded Accept-Ranges metadata while preserving raw headers No Range request generation, multipart range serialization, partial response engine, automatic retry/replay, redirect behavior, cache storage or policy, filesystem serving, MIME detection, automatic cache validation, automatic static-file policy, automatic byte serving, content slicing, download resume, or status-policy behavior
Conditional requests Request::evaluate_conditional, evaluate_conditional_request, HttpConditionalMetadata, and HttpEntityTag evaluate bounded HTTP/1.1 validators; HttpResponse::not_modified and precondition_failed serialize 304 and 412 outcomes No cache storage, static-file serving policy, automatic revalidation, or cache-control engine
Informational responses and Early Hints HttpResponse::early_hints and early_hints_with_headers construct validated bodyless 103 Early Hints response metadata with bounded Link and safe metadata headers 101 Switching Protocols remains a separate terminal handoff response; no automatic preload execution, cache policy, redirect/retry/replay, route generation, streaming early-write API, TLS/ALPN behavior, or status-policy behavior
Cache-Control Request::cache_control, HttpRequest::cache_control, and HttpResponse::cache_control parse bounded request/response directives, numeric freshness fields, quoted field-name lists, and extension directives; HttpResponse::with_age/age, with_expires/expires, and with_retry_after_delta/with_retry_after_date/retry_after declare and parse response Age, Expires, and Retry-After metadata No cache storage, automatic revalidation, wall-clock freshness calculation, Vary matching, shared-cache policy enforcement, automatic conditional requests, directive-based validator evaluation, automatic sleep, retry, replay, backoff, scheduler integration, or status-code policy engine
Vary HttpVary, HttpResponse::with_vary, HttpResponse::vary, Request::vary_selection, and HttpRequest::vary_selection parse, declare, and select bounded Vary metadata with case-insensitive field-name handling No cache storage, stored-response matching engine, cache key persistence, automatic request replay, shared-cache policy enforcement, or automatic conditional requests
Allow HttpAllowedMethods, HttpResponse::with_allow, and HttpResponse::allow declare and parse bounded Allow method-list metadata No route dispatch, automatic 405 generation, OPTIONS policy, fallback method selection, retry/replay, or status-code policy engine
Content-Language HttpContentLanguages, HttpResponse::with_content_language, and HttpResponse::content_language declare and parse bounded Content-Language response metadata No automatic language negotiation, route selection, locale fallback, variant matching, cache policy, retry, replay, redirect, or status-policy behavior
Content-Location HttpResponse::with_content_location declares one bounded singleton Content-Location header, and HttpResponse::content_location parses attached singleton response metadata while preserving raw headers No redirect behavior, cache variant selection, representation replacement, retry/replay, route generation, or status-policy behavior
Content-Type and Content-Encoding HttpContentType, HttpResponse::with_content_type, content_type, HttpResponseContentEncodings, HttpResponse::with_content_encoding, and content_encoding declare and parse bounded representation metadata while preserving raw headers on parse failures and replacing raw duplicates on typed declaration No MIME sniffing, body decoding, charset transcoding, compression/decompression, negotiation, cache policy, redirects, retry/replay, or filesystem serving
Content-Disposition HttpContentDisposition, HttpResponse::with_content_disposition, with_attachment_filename, and content_disposition declare and parse bounded singleton Content-Disposition response metadata, preserve parsed filename and filename* parameter values, preserve raw headers on parse failures, and replace raw duplicates on typed declaration No automatic download, filesystem path handling, MIME sniffing, cache behavior, redirect behavior, retry/replay, negotiation, or status-policy behavior
Upgrade and tunnel targets CONNECT authority-form requests are accepted as HTTP requests; HttpResponse::upgrade can hand an upgraded socket to caller code after a matching request The server does not implement the upgraded protocol after handoff
Trailers Chunked request trailers are preserved on Request; malformed, oversized, forbidden, and pseudo-header trailers are rejected; response trailers can be serialized for chunked responses Application metadata trailers are allowed; trailer names that affect connection state, routing, authentication/cookies, framing, or payload processing are rejected
Bounded h2c server The same socket2 listener detects the HTTP/2 prior-knowledge preface or a valid HTTP/1.1 Upgrade: h2c request with HTTP2-Settings, validates SETTINGS including legal SETTINGS_ENABLE_PUSH and SETTINGS_ENABLE_CONNECT_PROTOCOL values of only 0 or 1 and legal SETTINGS_MAX_FRAME_SIZE values from 16,384 through 16,777,215 bytes, dispatches RFC 8441 extended CONNECT only after SETTINGS_ENABLE_CONNECT_PROTOCOL = 1 has been negotiated, exposes negotiated extended CONNECT as a normal Request with method CONNECT, version HTTP/2, target from :path, host from :authority, and Request::extended_connect_protocol() from :protocol, advertises the default 16,384-byte SETTINGS_MAX_FRAME_SIZE, rejects inbound frames above the active local limit, splits outbound HEADERS, DATA, and trailers to the active peer frame-size limit, advertises SETTINGS_MAX_CONCURRENT_STREAMS from the bounded active stream allowance, enforces that allowance before dispatching new streams, advertises and enforces a conservative SETTINGS_MAX_HEADER_LIST_SIZE for inbound request metadata, bounds HPACK dynamic table use with SETTINGS_HEADER_TABLE_SIZE, serves bounded streams including bodyless DELETE, OPTIONS, TRACE, and negotiated extended CONNECT, handles HEAD without response DATA, rejects connection-specific request fields before handler dispatch, strips connection-specific response fields during h2c serialization, treats RST_STREAM as a bounded reset/cancellation signal for the affected stream, acknowledges inbound PING without ACK on stream 0 and exactly 8 octets with matching opaque data, ignores inbound PING ACK, rejects malformed PING frames, accepts padded HEADERS/DATA/trailers without exposing padding, handles HPACK Huffman fields and bounded CONTINUATION header blocks, emits GOAWAY with the last completed stream id at bounded shutdown, validates and ignores valid PRIORITY metadata, ignores HTTP/2-allowed unknown/extension frames inside this bounded path, normalizes reserved stream-id high bits, and applies conservative DATA flow control Ordinary CONNECT, missing-negotiation :protocol, non-CONNECT :protocol, malformed h2c Upgrade, request bodies on h2c Upgrade, and PUSH_PROMISE are rejected deterministically before handler dispatch; HTTP/1.1 CONNECT and non-h2c Upgrade remain separate handoff paths; bounded h2c only, with no keepalive timers, no automatic client/server initiated PING policy, no public cancellation callback API, no dynamic policy API, no extension callback API, no full extension negotiation, TLS ALPN, external h2 integration, full WebSocket-over-h2, proxy h2, tunnel handoff, connection pooling, persistent multiplex sessions, persistent HTTP/2 session management, automatic retry/replay, server push, full RFC 8441 support, full session manager, full stream state machine, full multiplex scheduler, unbounded multiplexing, unbounded multiplex scheduling, general multiplexing, general tunnel scheduling, priority scheduling, or full HTTP/2 server feature set

About

http lib for rust

Resources

License

Stars

2 stars

Watchers

1 watching

Forks

Packages

 
 
 

Contributors

Languages