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
- Raise the limit in Rocket.toml: [default.limits] forms = "2 MiB" (or limits = { forms = "2 MiB" })
- Switch file-upload fields to TempFile<'_> or Data<'_> which stream to disk instead of parsing through the forms limit
- Use Capped<Form<T>> as the guard to accept oversized-but-parseable input and branch on is_complete()/n
- 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
- Set limits.forms in Rocket.toml deliberately for every deployment, don't rely on the 32 KiB default
- Route file uploads through TempFile<'_>/Data<'_>, not Form
- Client-side: send multipart for anything binary or large
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.