seanmonstar/warp · warning · InvalidHeader
invalid_header
Error message
invalid_header
What it means
This is warp's reject::invalid_header rejection raised by header::optional (src/filters/header.rs:94). The named header was present in the request, but its value failed to parse into the target type T (via FromStr). Unlike a missing header — which optional() turns into None — a present-but-unparseable header is an error rejection. Note the public optional2 variant maps the failure into Ok(None) instead.
Solutions
- Inspect the actual header value the client sends (curl -v) and compare with T's FromStr expectations
- Trim whitespace or normalize the value client-side before sending
- Use header::optional2 (or your own map over optional()) to treat unparseable as None instead of a rejection
- Use a custom newtype with a lenient FromStr implementation to accept alternate formats
- Return a 400 with a clear message by handling the invalid_header rejection in recover()
Example fix
// before
let len = warp::header::<u64>("x-items");
// after (tolerate missing or malformed header)
let len = warp::header::optional2::<u64>("x-items")
.map(|opt| opt.unwrap_or(0)); Defensive patterns
Strategy: validation
Validate before calling
// validate header format client-side before sending
const items = "42";
if (!/^\d+$/.test(items)) throw new Error("x-items must be a positive integer");
headers["x-items"] = items; Type guard
fn parse_header_u64(v: &str) -> Option<u64> { v.trim().parse::<u64>().ok() } Try / catch
.recover(|rej: warp::Rejection| async move {
if let Some(e) = rej.find::<warp::reject::InvalidHeader>() {
Ok(warp::reply::with_status(
format!("invalid value for header: {}", e.name()),
warp::http::StatusCode::BAD_REQUEST,
))
} else {
Err(rej)
}
}) Prevention
- Document the exact header format expected by each route
- Trim/normalize header values on the client before sending
- Use optional2 when an unparseable header should degrade to None
- Write integration tests that exercise each typed header extraction
- Handle InvalidHeader in recover() to return a descriptive 400 instead of a bare rejection
When it happens
Trigger: warp::header::<T>("x-custom") or the optional()/optional2 chain where T's FromStr::from_str fails, e.g. warp::header::<u64>("content-length") with a non-numeric value, a datetime header in an unexpected format, or a header value with invalid characters for the type.
Common situations: Client sends content-length or a custom numeric header with garbage/extra whitespace; version drift where the client changed a header format; internationalized or URL-encoded header values; parsing Date/If-Modified-Since headers whose format differs from the expected RFC form.
Related errors
AI-assisted analysis of seanmonstar/warp@ff34d7213e (2026-09-09).
Data as JSON: /api/errors/a502586daa889049.
Report an issue: GitHub.
Appendix: source
Thrown at src/filters/header.rs:94
pub fn optional<T>(
name: &'static str,
) -> impl Filter<Extract = One<Option<T>>, Error = Rejection> + Copy
where
T: FromStr + Send + 'static,
{
filter_fn_one(move |route| {
tracing::trace!("optional({:?})", name);
let result = route.headers().get(name).map(|value| {
value
.to_str()
.map_err(|_| reject::invalid_header(name))?
.parse::<T>()
.map_err(|_| reject::invalid_header(name))
});
match result {
Some(Ok(t)) => future::ok(Some(t)),
Some(Err(e)) => future::err(e),
None => future::ok(None),
}
})
}
pub(crate) fn optional2<T>() -> impl Filter<Extract = One<Option<T>>, Error = Infallible> + Copy
where
T: Header + Send + 'static,
{
filter_fn_one(move |route| future::ready(Ok(route.headers().typed_get())))
}
/* TODO
pub fn exact2<T>(header: T) -> impl FilterClone<Extract=(), Error=Rejection>
where
T: Header + PartialEq + Clone + Send,
{
filter_fn(move |route| {View on GitHub (pinned to ff34d7213e)