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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
| 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>>(())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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
| 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 |