Skip to main content

Crate http_extract

Crate http_extract 

Source
Expand description

Strict, trust-aware extraction of HTTP request metadata.

This crate operates primarily on http types and does not require a web framework, Tower, tracing, or OpenTelemetry. An opt-in axum feature adds only transport-peer extraction from Axum request extensions. Forwarding fields are never trusted implicitly.

Public modules expose small synchronous extractors for one coherent field responsibility. Applications compose only the modules they need at their framework boundary; this crate does not impose a request-context aggregate or an asynchronous adapter. Cargo features gate field-specific modules; disabling default features leaves the shared header helpers and Error available, and enabling a feature exposes only its documented module and dependencies.

§http-extract

http-extract provides strict extraction of HTTP request metadata. Its narrow Forwarded support extracts RFC 7239 for= IP chains. The default core API uses types from the http crate and does not depend on Axum, Tower, tracing, or OpenTelemetry. A non-default axum feature adds only the request-extension peer adapter.

The crate separates two things that are easy to conflate:

  • facts carried directly by a request URI or transport adapter;
  • untrusted assertions parsed from forwarding, proxy, or provider fields.

Forwarding fields are never trusted by default. Authorization, API keys, cookies, request bodies, and complete query strings are never extracted implicitly.

The mdBook guide source is in docs; build it locally with mdbook build.

§Standards and compatibility

The crate follows the stable RFC versions below only for the extraction behavior its APIs document. It is not a complete HTTP or authentication implementation.

X-Forwarded-For, X-Forwarded-Proto, CF-Connecting-IP, CloudFront-Viewer-Address, Fly-Client-IP, True-Client-IP, X-Envoy-External-Address, and X-Real-IP are common de facto or vendor fields, not IETF standards. Their extractors only parse raw assertions; a deployment must establish trust independently.

The declared Rust compatibility baseline is Rust 1.96.0, matching package.rust-version and rust-toolchain.toml. Default features enable every documented extraction module. With default-features = false, consumers opt into the modules they need; see Cargo features and the mdBook compatibility chapter.

§Request, headers, and Parts

Extraction functions accept the smallest input that carries the required fact. This supports Tower’s borrowed Request<B> and Axum-style FromRequestParts code without a framework trait:

use http::Request;
use http_extract::authority::{extract_header_authority, extract_request_authority};

let request = Request::builder()
    .uri("https://example.com/items")
    .header("host", "fallback.example")
    .body(())?;

let authority = extract_request_authority(&request)?;
assert_eq!(authority.unwrap().as_str(), "example.com");

let (parts, _) = request.into_parts();
let host = extract_header_authority(&parts.headers)?;
assert_eq!(host.unwrap().as_str(), "fallback.example");

§Module and API map

Each public module owns one coherent extraction responsibility:

  • authority::extract_request_authority returns a complete request’s URI authority, consulting Host only when the URI has no authority;
  • authority::extract_header_authority strictly extracts only the Host authority;
  • content_type::extract_header_content_type parses the singular Content-Type field, with extract_request_content_type as a request convenience;
  • request_id::extract_header_request_id prefers X-Request-Id and falls back to Request-Id; extract_request_request_id delegates from a request;
  • forwarded::extract_header_forwarded_for extracts the standardized, untrusted Forwarded for= IP chain;
  • x_forwarded separately parses the de facto X-Forwarded-For and X-Forwarded-Proto conventions as untrusted assertions;
  • client_ip::extract_peer_ip returns an out-of-band transport peer IP; extract_client_ip uses the documented default Header order, while extract_client_ip_with_headers accepts caller-defined ClientIpHeader sources in order;
  • client_ip_headers contains direct raw extraction for common single-value provider/proxy IP fields; these values are never trusted automatically;
  • authorization::extract_header_authorization returns the raw singular field, while extract_request_authorization provides the same behavior for a complete request; the module’s Bearer and Basic helpers only route matching schemes to raw credential strings;
  • api_key::extract_header_api_key reads the fixed API-key fields, while extract_request_api_key provides the same policy for a complete request;
  • header::extract_single_header_value, extract_single_header_text, and append_header_value contain shared strict field utilities.

All APIs are synchronous and operate on the smallest relevant http input. Applications select and compose only the extractors they need at their own framework boundary:

use http::Request;
use http_extract::{
    authority::extract_request_authority,
    client_ip::extract_client_ip,
    content_type::extract_header_content_type,
    request_id::extract_header_request_id,
};

let request = Request::builder()
    .uri("https://example.com/items?token=not-collected")
    .header("cf-connecting-ip", "198.51.100.9")
    .header("x-request-id", "request-123")
    .header("content-type", "application/json")
    .body(())?;
let client = extract_client_ip(request.headers())?.unwrap();
assert_eq!(client.to_string(), "198.51.100.9");
assert_eq!(
    extract_request_authority(&request)?.unwrap().as_str(),
    "example.com",
);
assert_eq!(
    extract_header_request_id(request.headers())?,
    Some("request-123")
);
assert_eq!(
    extract_header_content_type(request.headers())?.unwrap(),
    mime::APPLICATION_JSON,
);

The example policy treats X-Request-Id as preferred and Request-Id as its fallback. They are application-configured field names, not a claim that both are universal IETF-standard request-ID fields.

§Client IP selection

use http::HeaderMap;
use http_extract::{
    client_ip::{ClientIpHeader, extract_client_ip, extract_client_ip_with_headers},
};

let mut headers = HeaderMap::new();
headers.insert("cf-connecting-ip", "198.51.100.1".parse()?);
headers.insert("x-real-ip", "198.51.100.2".parse()?);

assert_eq!(extract_client_ip(&headers)?.unwrap().to_string(), "198.51.100.2");
assert_eq!(
    extract_client_ip_with_headers(
        &headers,
        &[ClientIpHeader::CfConnectingIp, ClientIpHeader::XRealIp],
    )?
    .unwrap()
    .to_string(),
    "198.51.100.1",
);

The default order is RFC 7239 Forwarded, X-Forwarded-For, X-Real-IP, then CF-Connecting-IP. This standard-first precedence is a library convention, not an RFC-defined precedence or trust policy. extract_client_ip_with_headers accepts the same supported sources in any order as ClientIpHeader values. Runtime configuration strings can be converted once with FromStr or ClientIpHeader::try_from; invalid and unsupported names return Error::UnsupportedHeaderName. Only an absent source falls through: a malformed first-present source returns an error. For the two chain fields, selection returns the rightmost address.

Both functions inspect Headers only. Their result is a raw, untrusted assertion and must not be used for authorization or rate limiting unless the deployment separately guarantees that the relevant proxy overwrites the selected field. Use extract_peer_ip when the transport peer itself is the desired network fact.

§Common single-value client IP fields

client_ip_headers provides direct Header and Request functions for:

  • CF-Connecting-IP;
  • CloudFront-Viewer-Address, including IPv4 and IPv6 IP:port forms;
  • Fly-Client-IP;
  • True-Client-IP;
  • X-Envoy-External-Address;
  • X-Real-IP.

For example, extract_header_cf_connecting_ip and extract_request_cf_connecting_ip return Option<IpAddr>. The other field names follow the same extract_header_* / extract_request_* convention. Every field is strict and singular: duplicates, non-text values, and malformed addresses fail without including the value in the error.

These are provider or proxy conventions, not independently trustworthy facts. The default selector includes CF-Connecting-IP and X-Real-IP; other fields can be selected explicitly with extract_client_ip_with_headers. Selection does not authenticate the sender or establish a trust boundary.

§Header utility

header::append_header_value is a thin wrapper around HeaderMap::append. It accepts already validated HeaderName and HeaderValue values, never replaces existing field lines, and returns whether the map already contained the name. Because the typed inputs are validated before the call, appending is infallible and does not create a diagnostic path that could echo a value.

§Authorization

authorization::extract_header_authorization returns the singular Authorization field unchanged, including an empty value. authorization::extract_request_authorization delegates the same extraction for a complete Request<B>. The extract_header_bearer_token and extract_header_basic_credentials helpers perform only ASCII case-insensitive scheme routing and require one or more ASCII spaces before the credentials. They consume only the first required space and return the rest unchanged, preserving any additional leading spaces. Their extract_request_* counterparts delegate from a complete request. A missing separator or another scheme returns None, while Bearer and Basic return Some(""). Returned values are sensitive and must not be logged or echoed; duplicate and non-text fields return errors without containing the field value.

For standards context, see the HTTP authentication framework in RFC 9110 Section 11, Bearer usage in RFC 6750 Section 2.1, and Basic in RFC 7617 Section 2. The crate only recognizes the scheme and extracts the raw credential string; it does not implement authentication, complete RFC syntax validation, or Basic decoding.

§API keys

api_key::extract_header_api_key reads X-API-Key first and falls back to Api-Key only when the preferred field is absent. It returns the selected value unchanged, including Some("") for an empty field. api_key::extract_request_api_key applies exactly the same behavior to a complete Request<B>. These functions select and decode a field; they do not authenticate the key. Returned values are sensitive and must not be logged or echoed. Duplicate selected fields and non-text values return errors that do not contain the field value.

§Request IDs

request_id::extract_header_request_id uses the fixed common precedence X-Request-Id first, then Request-Id only when the preferred field is absent. An empty preferred value is preserved and stops fallback. The Request function only delegates to the Header function. The crate does not generate or validate request IDs, and these configured names are not presented as universal IETF standard fields.

§Cargo features

Default features enable the complete common API. Applications can disable them and opt into only the modules they use:

[dependencies]
http-extract = { version = "0.1.0-alpha.0", default-features = false, features = [
  "authority",
  "content-type",
] }

Available module features are api-key, authority, authorization, axum, client-ip, client-ip-headers, content-type, forwarded, request-id, and x-forwarded. client-ip enables client-ip-headers, forwarded, and x-forwarded because its selectors use those parsing modules. The non-default axum feature enables client-ip and the optional Axum dependency so client_ip::extract_axum_peer_address and client_ip::extract_axum_peer_ip can read a socket peer from a request extension. content-type enables the optional mime dependency. The crate-wide Error and generic header utilities remain available with --no-default-features.

§Errors and sensitive values

Every fallible public operation uses the crate-wide http_extract::Error. Missing optional metadata is Ok(None). Callers only need to handle the small top-level categories InvalidHeader and DuplicateHeader. Errors identify the affected Header without exposing parser details or field values.

Errors never contain Header values. Raw Authorization and API-key values are returned only through their explicit extractors and must not be logged or echoed.

§example

examples/axum shows an Axum request boundary. With --features axum, the handler obtains the transport peer using client_ip::extract_axum_peer_address and extract_axum_peer_ip, calls only the independent extractors it needs, and returns a safe JSON summary.

Modules§

api_key
API-key extraction from the fixed X-API-Key and Api-Key fields.
authority
Request authority and Host field extraction.
authorization
Raw extraction of the sensitive Authorization field.
client_ip
Best-effort extraction of raw client IP assertions.
client_ip_headers
Direct extraction of common single-value client IP fields.
content_type
Content-Type extraction.
forwarded
Strict extraction of untrusted client IP assertions from Forwarded.
header
Strict building blocks for HTTP field maps.
request_id
Request ID extraction with fixed common field precedence.
x_forwarded
Parsing of untrusted X-Forwarded-* header conventions.

Structs§

HeaderMap
A specialized multimap for header names and values.
HeaderName
Represents an HTTP header field name
HeaderValue
Represents an HTTP header field value.
Request
Represents an HTTP request.

Enums§

Error
An error produced while extracting request metadata.