rwf2/Rocket · error · io::Error

UnexpectedEof

UnexpectedEof

Error message

data limit exceeded

What it means

Runtime error produced by the impl_from_data_capped!-style macro in core/lib/src/data/capped.rs: a FromData guard (e.g. strict Form<T>) wraps the raw data in Capped<T>, and when the stream completes but the guard reports is_complete() == false — i.e. the configured data limit was hit before the body ended — the wrapper returns an io::Error of kind UnexpectedEof ('data limit exceeded') and the request fails with 400 BadRequest. The limit comes from Rocket's limits config (e.g. limits.forms, default 32 KiB).

Source

Thrown at core/lib/src/data/capped.rs:262

}

macro_rules! impl_strict_from_data_from_capped {
    ($T:ty) => (
        #[crate::async_trait]
        impl<'r> $crate::data::FromData<'r> for $T {
            type Error = <$crate::data::Capped<Self> as $crate::data::FromData<'r>>::Error;

            async fn from_data(
                r: &'r $crate::Request<'_>,
                d: $crate::Data<'r>
            ) -> $crate::data::Outcome<'r, Self> {
                use $crate::outcome::Outcome::*;
                use std::io::{Error, ErrorKind::UnexpectedEof};

                match <$crate::data::Capped<$T> as FromData>::from_data(r, d).await {
                    Success(p) if p.is_complete() => Success(p.into_inner()),
                    Success(_) => {
                        let e = Error::new(UnexpectedEof, "data limit exceeded");
                        Error((Status::BadRequest, e.into()))
                    },
                    Forward(d) => Forward(d),
                    Error((s, e)) => Error((s, e)),
                }
            }
        }
    )
}

View on GitHub (pinned to 3a54d079ae)

Solutions

  1. Raise the limit in Rocket.toml: [default.limits] forms = "2 MiB" (or limits = { forms = "2 MiB" })
  2. Switch file-upload fields to TempFile<'_> or Data<'_> which stream to disk instead of parsing through the forms limit
  3. Use Capped<Form<T>> as the guard to accept oversized-but-parseable input and branch on is_complete()/n
  4. Register a 400/413 catcher to return a friendly message when a client still exceeds the limit

Example fix

# before
# Rocket.toml (defaults: forms = 32 KiB)
#[post("/submit", data = "<form>")]
fn submit(form: Form<Report>) { }

# after
# Rocket.toml
[default.limits]
forms = "2 MiB"
files = "10 MiB"

#[post("/submit", data = "<form>")]
fn submit(form: Form<Report>) { }
Defensive patterns

Strategy: try-catch

Validate before calling

// reject oversized form bodies before the guard parses them
#[post("/submit", data = "<data>")]
fn submit(data: Data<'_>) -> Status {
    let forms_limit = 2 * 1024 * 1024;
    match data.open(forms_limit.into()).into_string().await {
        Ok(s) if s.is_complete() => { /* parse manually or forward */ Status::Ok }
        Ok(_) => Status::PayloadTooLarge,
        Err(_) => Status::BadRequest,
    }
}

Try / catch

// accept-and-inspect oversized input instead of failing hard
#[post("/submit", data = "<form>")]
fn submit(form: Capped<Form<Report>>) -> Status {
    if form.is_complete() { Status::Ok } else { Status::PayloadTooLarge }
}

// and a catcher for other routes
#[catch(400)]
fn bad_request(_: &Request) -> &'static str { "payload too large or malformed" }

Prevention

When it happens

Trigger: POSTing a multipart/form or urlencoded body larger than limits.forms (default 32 KiB) to a route with a Form<T> guard; any capped guard whose limit the client exceeds. The boundary check is exact: hitting the cap without a terminating boundary yields an incomplete Capped value.

Common situations: File uploads routed through Form<T> instead of TempFile/Data; raising expectations in dev with small test payloads then receiving real-world sizes; forgetting Rocket's conservative 32 KiB default for forms after upgrading from an older version.

Related errors


AI-assisted analysis of rwf2/Rocket@3a54d079ae (2026-08-16). Data as JSON: /api/errors/2a67fffa18758fdd. Report an issue: GitHub.