denoland/deno · error
unexpected EOF while reading HTTP/1 body
Error message
unexpected EOF while reading HTTP/1 body
What it means
Deno's HTTP/1 implementation (libs/http_h1, backing fetch and HTTP handling): while draining a response body, if the connection reaches EOF while body parsing still reports Partial — Content-Length bytes unread, or a chunked body without its terminating chunk — the read fails with ErrorKind::UnexpectedEof via this helper instead of returning a short body silently.
Source
Thrown at libs/http_h1/conn.rs:1556
consumed: usize,
},
Complete {
consumed: usize,
},
Partial {
consumed: usize,
},
}
fn protocol_error(error: ProtocolError) -> Error {
match error {
ProtocolError::Parse(error) => Error::Parse(error),
ProtocolError::HeadTooLarge => Error::HeadTooLarge,
}
}
fn unexpected_eof() -> Error {
Error::Io(io::Error::new(
io::ErrorKind::UnexpectedEof,
"unexpected EOF while reading HTTP/1 body",
))
}
fn body_status_from_buf(
protocol: &mut Protocol,
buf: &[u8],
) -> Result<ConnBodyStatus, Error> {
let status = protocol.body_chunk(buf).map_err(protocol_error)?;
Ok(match status {
BodyStatus::Chunk { bytes, consumed } => ConnBodyStatus::Chunk {
offset: consumed - bytes.len(),
len: bytes.len(),
consumed,
},
BodyStatus::Complete { consumed } => ConnBodyStatus::Complete { consumed },
BodyStatus::Partial { consumed } => ConnBodyStatus::Partial { consumed },View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Retry idempotent requests (GET) with backoff — this class of failure is transient under keep-alive races.
- If you control the server, verify Content-Length computation and that the full body flushes before the handler returns; disable output-compression middleware that miscounts.
- Check intermediate proxies/LBs for response timeouts smaller than large transfers take.
- For streams that cannot be retried, read the body incrementally and persist progress so a truncated transfer resumes instead of restarting.
Example fix
// before
const res = await fetch(url);
const body = await res.arrayBuffer(); // may throw: unexpected EOF while reading HTTP/1 body
// after
async function fetchRetry(url, tries = 3) {
for (let i = 0; ; i++) {
try { const r = await fetch(url, { signal: AbortSignal.timeout(30_000) }); return await r.arrayBuffer(); }
catch (e) {
if (i === tries - 1 || !(e instanceof TypeError || /unexpected EOF/.test(String(e.cause ?? e)))) throw e;
await new Promise(r => setTimeout(r, 2 ** i * 500));
}
}
} Defensive patterns
Strategy: retry
Validate before calling
const res = await fetch(url, { signal: AbortSignal.timeout(30_000) });
const declared = Number(res.headers.get("content-length") ?? -1);
if (declared === 0) throw new Error(`server declared empty body for ${url}`); // suspicious early-close setup Try / catch
try { body = await res.arrayBuffer(); } catch (e) { if (/unexpected EOF|network/.test(String(e.cause ?? e))) { await sleep(backoff(i)); return fetchRetry(url, i + 1); } throw e; } Prevention
- Retry idempotent requests with backoff; truncated-body failures are usually transient keep-alive races.
- If you own the server, verify Content-Length math (especially with compression middleware) and flush full bodies before handler exit.
- Use incremental body reads with persisted progress for large transfers so a cut connection resumes instead of restarting.
- Watch proxy/LB response timeouts vs. your largest responses.
When it happens
Trigger: A server or proxy closes the keep-alive connection before the promised HTTP/1 body is complete: Content-Length larger than the actual bytes sent, chunked stream cut before the 0-chunk terminator, mid-response timeouts, or connection races where a reused socket is closed server-side right as a response streams.
Common situations: Misbehaving backends that send wrong Content-Length; CDN/proxy idle timeouts cutting long responses; keep-alive reuse races against server-side reaping; debugging tools closing sockets; mobile networks dropping mid-download.
Related errors
- fetch failed
- Return value from serve handler must not be an error respons
- ERR_SOCKET_CLOSED
- unexpected end of bytes
- Deno.autoUpdate: check failed:
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/f398fa79f629748b.
Report an issue: GitHub.