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

  1. Inspect the actual header value the client sends (curl -v) and compare with T's FromStr expectations
  2. Trim whitespace or normalize the value client-side before sending
  3. Use header::optional2 (or your own map over optional()) to treat unparseable as None instead of a rejection
  4. Use a custom newtype with a lenient FromStr implementation to accept alternate formats
  5. 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

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)