http-extract
Extract the signal. Keep trust explicit.
http-extract is a synchronous, framework-independent Rust library for strict
HTTP request metadata extraction. Its public functions accept http crate
types and return ordinary Rust values such as &str, u64, Mime,
Authority, IpAddr, and Vec<IpAddr>.
One small API, one hard boundary
The library deliberately separates two kinds of information:
| Request and transport facts | Raw Header assertions |
|---|---|
| URI authority, Content-Type, request ID, socket peer | Forwarded, X-Forwarded-*, provider client-IP fields |
| Read through small, direct functions | Parsed without silently granting trust |
Forwarding fields are never trusted merely because they are present. Errors contain field names, never parser details or raw field values. Authorization credentials, API keys, cookies, bodies, complete query strings, and raw forwarding fields must not be logged.
Start here
- Install the crate and run a first extraction.
- Understand the client IP trust boundary.
- Find the feature and API family you need.
- Run the production-oriented Axum example.
The API documentation is the complete type-level reference. The exact protocol boundary is documented in Standards and compatibility.
Framework boundary
The default library core is synchronous and framework-independent. Axum is an optional, non-default dependency used only for the transport-peer extension adapter and the runnable example. The declared compiler baseline is Rust 1.96.0.
Getting started
The crate declares Rust 1.96.0 as its compiler compatibility baseline. Default features provide the complete documented extraction API.
With all common API families enabled:
[dependencies]
http-extract = "0.1"
For a smaller dependency surface, disable defaults and enable only the features you use:
[dependencies]
http-extract = { version = "0.1", default-features = false, features = [
"authority",
"content-type",
] }
Header functions contain the parsing logic. Request functions are convenience
wrappers that only delegate through request.headers():
#![allow(unused)]
fn main() {
use http_extract::{Request, extract_request_authority, extract_request_content_type};
let request = Request::builder()
.uri("https://example.com/items")
.header("content-type", "application/json")
.body(())?;
assert_eq!(
extract_request_authority(&request)?.unwrap().as_str(),
"example.com"
);
assert_eq!(
extract_request_content_type(&request)?.unwrap().essence_str(),
"application/json",
);
Ok::<(), Box<dyn std::error::Error>>(())
}
A socket peer is not an HTTP field. Obtain it from the server adapter. If the
adapter stores a SocketAddr directly in request extensions, use
extract_request_socket_address or
extract_request_socket_ip. With the non-default axum feature,
extract_axum_socket_address reads an existing ConnectInfo<SocketAddr> request
extension, while extract_axum_socket_ip returns its IP component; neither
fabricates that fact. extract_socket_ip composes the Axum and direct extension
sources without reading Headers.
The Header-based extract_client_ip convenience does not use or authenticate
the transport peer, so its result remains untrusted. For an explicitly
trusted-proxy deployment, extract_proxy_client_ip checks the default Header
order and falls back to extract_socket_ip only when all Headers in
CLIENT_IP_HEADERS are absent. It does not verify the proxy trust boundary.
Before deployment, review Standards and compatibility, Features, and the client IP trust boundary.
Client IP trust boundary
The transport peer is the only network fact available independently of HTTP
Headers. Forwarded, X-Forwarded-For, and provider-specific client IP fields
are caller-controlled assertions until a deployment establishes trust.
extract_client_ip(headers) checks these fields in order:
- RFC 7239
Forwarded; X-Forwarded-For;X-Real-IP;CF-Connecting-IP.
This standard-first precedence is a library convention, not an order or trust
policy defined by an RFC. Only an absent source falls through. A malformed
first-present source returns an error instead of consulting a lower-priority
field. Forwarded and
X-Forwarded-For contribute the rightmost address, representing the assertion
nearest the server.
Use extract_client_ip_with_headers(headers, order) with an ordered
ClientIpHeader slice to choose different sources or precedence. The selector
also supports the other single-value fields documented in
Common client IP fields.
Configuration strings can be parsed into ClientIpHeader values with
FromStr; unsupported names fail before request extraction.
Neither selector receives the transport peer or trusted CIDRs. The returned IP is therefore raw and untrusted. Before using it for authorization, rate limiting, or audit decisions, restrict the application listener to controlled proxies and ensure that the selected field is overwritten according to the deployment’s policy.
extract_proxy_client_ip(request) composes the default Header selection with
peer fallback. It calls extract_socket_ip(request) only when every supported
Header is absent; an invalid first-present Header remains an error. The peer
helper prefers Axum ConnectInfo<SocketAddr> when the feature is enabled and
then a direct SocketAddr extension. Neither helper verifies trusted proxy
addresses or CIDRs. Use extract_socket_ip alone when the actual socket peer is
the desired fact.
extract_header_forwarded_for remains deliberately strict: the field must be
singular and every element must contain a usable IP for= value. Missing,
unknown, obfuscated, and non-IP nodes fail rather than being skipped.
Parameters other than for are ignored after quote-aware element splitting;
their names and values are not validated. See
RFC 7239 Section 5.2
and its
Section 8 security considerations.
X-Forwarded-For, X-Real-IP, and the provider-specific fields are de facto
or vendor conventions, not IETF standards.
Common client IP fields
The client-ip-headers feature provides one direct function per field:
extract_header_cf_connecting_ip/extract_request_cf_connecting_ip;extract_header_cloudfront_viewer_address/ matching Request function;extract_header_fly_client_ip/ matching Request function;extract_header_true_client_ip/ matching Request function;extract_header_x_envoy_external_address/ matching Request function;extract_header_x_real_ip/ matching Request function.
All return Result<Option<IpAddr>, Error>. Missing fields return None.
Fields are singular; duplicates, non-text values, and malformed addresses fail.
CloudFront-Viewer-Address accepts IPv4 and IPv6 IP:port forms, including
CloudFront’s unbracketed IPv6 representation.
These are vendor or de facto field names, not IETF standards. Extraction does
not authenticate the sender or make the value safe for access control,
logging, or rate limiting. The default extract_client_ip order includes
CF-Connecting-IP and X-Real-IP; the other sources can be selected through
extract_client_ip_with_headers. Selection does not authenticate the sender,
so applications must apply a deployment-specific trust policy before using a
result. See Standards and compatibility for the complete field
classification.
Features
The crate declares Rust 1.96.0 as its compiler compatibility baseline. Default
features enable the complete common extraction API: api-key, authority,
authorization, client-ip, client-ip-headers, content-type, forwarded,
request-id, and x-forwarded.
Every extraction feature owns one field family. Header functions contain the
parsing implementation; matching Request functions delegate through
request.headers(). All public APIs are exposed from the crate root.
Feature and API reference
| Cargo feature | Adds | Main APIs | Success value |
|---|---|---|---|
api-key | X-API-Key, then Api-Key extraction | extract_header_api_key, extract_request_api_key | Option<&str> |
authority | URI authority and strict Host extraction | extract_header_authority, extract_request_authority | Option<Authority> |
authorization | Raw Authorization and Bearer/Basic scheme routing | extract_*_authorization, extract_*_bearer_token, extract_*_basic_credentials | Option<&str> |
axum | Optional Axum ConnectInfo<SocketAddr> peer adapter | extract_axum_socket_address, extract_axum_socket_ip | Option<SocketAddr> or Option<IpAddr> |
client-ip | Socket-peer helpers and default/custom Header selection | extract_socket_ip, extract_client_ip, extract_client_ip_with_headers, extract_proxy_client_ip | Option<IpAddr> |
client-ip-headers | Common provider and proxy client-IP fields | one extract_header_* and extract_request_* pair per field | Option<IpAddr> |
content-type | Strict Content-Type parsing and the optional mime dependency | extract_header_content_type, extract_request_content_type | Option<Mime> |
forwarded | Strict RFC 7239 Forwarded for= IP chains | extract_header_forwarded_for, extract_request_forwarded_for | Option<Vec<IpAddr>> |
request-id | X-Request-Id, then Request-Id precedence | extract_header_request_id, extract_request_request_id | Option<&str> |
x-forwarded | Raw X-Forwarded-For and X-Forwarded-Proto parsing | extract_*_x_forwarded_for, extract_*_x_forwarded_proto | Option<Vec<IpAddr>> or Option<Vec<String>> |
The crate-wide Error and generic header utilities remain available with no
default features. header includes the strict singular-field helpers and
append_header_value, which appends without replacing existing field lines.
Client IP composition
extract_request_socket_address and extract_request_socket_ip read a
SocketAddr stored directly in request extensions. extract_socket_ip prefers
Axum ConnectInfo<SocketAddr> when the axum feature is enabled, then falls
back to the direct extension. None of these peer helpers inspect Headers.
extract_client_ip uses the documented default Header order, while
extract_client_ip_with_headers accepts an explicit ordered slice of
ClientIpHeader values. Both return raw, untrusted Header assertions.
extract_proxy_client_ip applies the default Header order and uses
extract_socket_ip only when every Header in CLIENT_IP_HEADERS is absent.
The non-default axum feature enables client-ip. Its
extract_axum_socket_address and extract_axum_socket_ip functions return
None when ConnectInfo<SocketAddr> is absent; they never fabricate a peer.
See the client IP trust boundary before using a Header-derived address for authorization, rate limiting, or auditing.
Feature relationships
client-ip enables client-ip-headers, forwarded, and x-forwarded because
its selectors use those parsing APIs. content-type enables the optional
mime dependency. The normal default dependency tree does not include Axum,
Tower, Tokio, tracing, or OpenTelemetry.
Enable the optional Axum adapter and runnable example explicitly:
cargo run --example axum-demo --features axum
Useful checks for consumers and contributors:
cargo check --no-default-features
cargo test --no-default-features --features authority,content-type,request-id
cargo test --no-default-features --features client-ip
cargo test --no-default-features --features client-ip-headers
This feature layout does not imply additional runtime compatibility. See Standards and compatibility for the protocol support boundary.
Request IDs
The request ID extractor uses a fixed practical precedence:
X-Request-Id;Request-Id, only when the preferred field is absent.
The selected value is returned unchanged. An empty X-Request-Id therefore
returns Some("") and does not fall back. Duplicate selected fields and
non-text values fail without including the value in the error.
#![allow(unused)]
fn main() {
use http_extract::{Request, extract_request_request_id};
let request = Request::builder()
.header("request-id", "fallback")
.header("x-request-id", "preferred")
.body(())?;
assert_eq!(
extract_request_request_id(&request)?,
Some("preferred")
);
Ok::<(), Box<dyn std::error::Error>>(())
}
These are crate-configured common field names, not a claim that either is a universal IETF-standard request ID field. The crate does not generate, validate, or propagate identifiers. The distinction from standardized fields is summarized in Standards and compatibility.
Standards and compatibility
http-extract implements narrow extraction behavior derived from the stable
RFC versions below. It does not claim complete HTTP, proxy, or authentication
protocol conformance.
IETF standards used
- HTTP Semantics, RFC 9110:
Section 5 informs
strict field handling;
Section 7.2 covers
request authority and
Host; Section 8.3 coversContent-Type; and Section 11 supplies the Authorization framework context. The crate only extracts these fields; it is not a complete HTTP implementation and does not authenticate. - Forwarded, RFC 7239: support is
intentionally limited to the field structure in
Section 4, the
for=parameter in Section 5.2, and IP node forms from Section 6.1.extract_header_forwarded_forreturns a continuous IP chain and rejects missing, unknown, obfuscated, or non-IP nodes instead of skipping them. It ignores parameters other thanforafter quote-aware element splitting without validating their names or values, and does not implement the full Forwarded object model. Parsing and Header-based selection do not establish trust; deployments must account for the Section 8 security considerations independently. - Bearer Token Usage, RFC 6750 Section 2.1: the helper performs ASCII case-insensitive scheme routing and returns the raw credential substring. It does not validate a token or authenticate.
- Basic Authentication, RFC 7617 Section 2: the helper performs ASCII case-insensitive scheme routing and returns the raw credential substring. It does not validate credentials, decode Base64, or authenticate.
Non-standard fields
The following are common vendor or de facto fields, not IETF standards:
X-Forwarded-ForandX-Forwarded-Proto;CF-Connecting-IP;CloudFront-Viewer-Address;Fly-Client-IP;True-Client-IP;X-Envoy-External-Address;X-Real-IP.
Their APIs only parse raw assertions. Presence does not authenticate a proxy or make a value suitable for access control, rate limiting, or audit use. See the client IP trust boundary for deployment guidance.
Rust and feature compatibility
The declared Rust baseline is Rust 1.96.0, matching Cargo.toml’s
rust-version and rust-toolchain.toml. This is a compiler compatibility
statement only; the core remains synchronous and framework-independent, and no
broader runtime compatibility is implied.
Default features enable all documented extraction API families. Consumers using
default-features = false can select only the features they need. The exact
feature list, dependency relationships, and copyable configuration are in
Features.
Axum integration example
The repository’s axum-demo example demonstrates the library at an Axum request
boundary while keeping the core framework-independent. Axum supplies the TCP
peer through ConnectInfo<SocketAddr>;
extract_axum_socket_address and extract_axum_socket_ip read that
request extension, while Header-derived client IP values remain separate raw
assertions.
Run the listener:
LISTEN_ADDR=127.0.0.1:3000 cargo run --example axum-demo --features axum
Or run the one-step in-process request demonstration:
cargo test --example axum-demo --features axum \
one_step_axum_request_demo_returns_complete_safe_json -- --nocapture
The handler passes request.headers() to extract_client_ip. That convenience
uses the library’s default Header order and returns a raw, untrusted assertion;
it does not authenticate a proxy. A successful request returns and logs one
JSON object containing the peer address/IP, selected Header IP/source,
authority, request ID, Content-Type, and masked Authorization, API-key, and
cookie values. Sensitive strings retain only their first and last two
characters (aa***bb); values of four characters or fewer are fully masked.
Missing optional values are JSON null.
The event never includes complete Authorization credentials, API-key values, or cookie content. It also omits raw forwarding fields, the complete query string, and body. Malformed selected metadata produces a generic HTTP 400 response.
Review the client IP trust boundary and Standards and compatibility before using a Header-derived IP for a security decision. Complete commands and the output contract are in the example README.