{"record":{"id":"f398fa79f629748b","repo":"denoland/deno","slug":"unexpected-eof-while-reading-http-1-body","errorCode":null,"errorMessage":"unexpected EOF while reading HTTP/1 body","messagePattern":"unexpected EOF while reading HTTP/1 body","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"libs/http_h1/conn.rs","lineNumber":1556,"sourceCode":"    consumed: usize,\n  },\n  Complete {\n    consumed: usize,\n  },\n  Partial {\n    consumed: usize,\n  },\n}\n\nfn protocol_error(error: ProtocolError) -> Error {\n  match error {\n    ProtocolError::Parse(error) => Error::Parse(error),\n    ProtocolError::HeadTooLarge => Error::HeadTooLarge,\n  }\n}\n\nfn unexpected_eof() -> Error {\n  Error::Io(io::Error::new(\n    io::ErrorKind::UnexpectedEof,\n    \"unexpected EOF while reading HTTP/1 body\",\n  ))\n}\n\nfn body_status_from_buf(\n  protocol: &mut Protocol,\n  buf: &[u8],\n) -> Result<ConnBodyStatus, Error> {\n  let status = protocol.body_chunk(buf).map_err(protocol_error)?;\n  Ok(match status {\n    BodyStatus::Chunk { bytes, consumed } => ConnBodyStatus::Chunk {\n      offset: consumed - bytes.len(),\n      len: bytes.len(),\n      consumed,\n    },\n    BodyStatus::Complete { consumed } => ConnBodyStatus::Complete { consumed },\n    BodyStatus::Partial { consumed } => ConnBodyStatus::Partial { consumed },","sourceCodeStart":1538,"sourceCodeEnd":1574,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/libs/http_h1/conn.rs#L1538-L1574","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst res = await fetch(url);\nconst body = await res.arrayBuffer(); // may throw: unexpected EOF while reading HTTP/1 body\n\n// after\nasync function fetchRetry(url, tries = 3) {\n  for (let i = 0; ; i++) {\n    try { const r = await fetch(url, { signal: AbortSignal.timeout(30_000) }); return await r.arrayBuffer(); }\n    catch (e) {\n      if (i === tries - 1 || !(e instanceof TypeError || /unexpected EOF/.test(String(e.cause ?? e)))) throw e;\n      await new Promise(r => setTimeout(r, 2 ** i * 500));\n    }\n  }\n}","handlingStrategy":"retry","validationCode":"const res = await fetch(url, { signal: AbortSignal.timeout(30_000) });\nconst declared = Number(res.headers.get(\"content-length\") ?? -1);\nif (declared === 0) throw new Error(`server declared empty body for ${url}`); // suspicious early-close setup","typeGuard":null,"tryCatchPattern":"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; }","preventionTips":["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."],"tags":["http","http1","fetch","eof","truncation","network"],"backgroundTag":"truncated-http-response","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-31T04:17:50.494Z"}