{"id":"111036985f028c04","repo":"hyperium/hyper","slug":"end-of-file-before-message-length-reached","errorCode":null,"errorMessage":"end of file before message length reached","messagePattern":"end of file before message length reached","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"src/proto/h1/decode.rs","lineNumber":161,"sourceCode":"\n    pub(crate) fn decode<R: MemRead>(\n        &mut self,\n        cx: &mut Context<'_>,\n        body: &mut R,\n    ) -> Poll<Result<Frame<Bytes>, io::Error>> {\n        trace!(\"decode; state={:?}\", self.kind);\n        match self.kind {\n            Length(ref mut remaining) => {\n                if *remaining == 0 {\n                    Poll::Ready(Ok(Frame::data(Bytes::new())))\n                } else {\n                    let to_read = usize::try_from(*remaining).unwrap_or(usize::MAX);\n                    let buf = ready!(body.read_mem(cx, to_read))?;\n                    let num = buf.as_ref().len() as u64;\n                    if num > *remaining {\n                        *remaining = 0;\n                    } else if num == 0 {\n                        return Poll::Ready(Err(io::Error::new(\n                            io::ErrorKind::UnexpectedEof,\n                            IncompleteBody,\n                        )));\n                    } else {\n                        *remaining -= num;\n                    }\n                    Poll::Ready(Ok(Frame::data(buf)))\n                }\n            }\n            Chunked {\n                ref mut state,\n                ref mut chunk_len,\n                ref mut extensions_cnt,\n                ref mut trailers_buf,\n                ref mut trailers_cnt,\n                ref h1_max_headers,\n                ref h1_max_header_size,\n            } => {","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/hyperium/hyper/blob/084473f728f9d07b3be5845475aa2f62ed9ff579/src/proto/h1/decode.rs#L143-L179","documentation":"Thrown in the HTTP/1 body decoder (src/proto/h1/decode.rs:161) as io::Error::new(UnexpectedEof, IncompleteBody) where IncompleteBody Display is 'end of file before message length reached' (decode.rs:681-687). It fires in the Length decoder when a read returns 0 bytes (EOF) before the full Content-Length body has been received (decode.rs:160-164). It is the specific, body-level form of 'incomplete message': the peer promised N bytes but closed early.","triggerScenarios":"A request/response has a Content-Length header but the connection EOFs after delivering fewer bytes; the Length decoder's read_mem returns an empty slice (num == 0) while remaining > 0 (decode.rs:156-164). Also produced in the chunked Body state when a chunk read returns 0 early (decode.rs:489-494).","commonSituations":"Upstream mis-reports Content-Length (too large) and closes after the real bytes; a proxy truncates the body; a client uploads then disconnects before finishing; TLS layer closes early; buffering middleware that miscalculates length.","solutions":["Confirm the peer actually sends exactly Content-Length bytes; fix length/transfer-encoding at the producer.","On the receiving side, treat UnexpectedEof from the body as a corrupt/partial body — discard it and retry idempotent requests.","If you proxy/transform bodies, recompute Content-Length (or use chunked) so you never advertise more than you send."],"exampleFix":"// before: producer advertises a length it may not deliver\nResponse::builder()\n    .header(\"content-length\", body_len.to_string())\n    .body(Body::from_stream(maybe_short_stream))?; // can EOF early\n\n// after: use chunked when the length isn't guaranteed, or ensure the stream completes\nResponse::builder()\n    // omit content-length; hyper will use chunked transfer-encoding\n    .body(Body::from_stream(maybe_short_stream))?","handlingStrategy":"retry","validationCode":"// Validate advertised length against your policy before trusting it.\nfn acceptable_content_length(headers: &HeaderMap) -> bool {\n    headers.get(\"content-length\")\n        .and_then(|v| v.to_str().ok())\n        .and_then(|s| s.parse::<usize>().ok())\n        .map(|n| n <= MAX_BODY_BYTES)\n        .unwrap_or(true)\n}","typeGuard":"fn is_truncated_body(err: &hyper::Error) -> bool {\n    // The IncompleteBody io::Error surfaces via Kind::Body; check the source.\n    matches!(\n        err.source().and_then(|s| s.downcast_ref::<std::io::Error>()).map(|io| io.kind()),\n        Some(std::io::ErrorKind::UnexpectedEof)\n    )\n}","tryCatchPattern":"match hyper::body::to_bytes(resp.into_body()).await {\n    Ok(bytes) => Ok(bytes),\n    Err(e) if is_truncated_body(&e) && idempotent => { /* discard, retry whole request */ }\n    Err(e) => Err(e),\n}","preventionTips":["Producers must advertise exact Content-Length or use chunked; never overstate the length.","Discard partial bodies on UnexpectedEof and retry idempotent requests.","If you transform bodies in a proxy, recompute Content-Length or switch to chunked."],"tags":["http1","body","content-length","eof","rust"],"analyzedSha":"084473f728f9d07b3be5845475aa2f62ed9ff579","analyzedAt":"2026-08-06T01:20:18.522Z","schemaVersion":2}