Deno 读完请求体仍关闭 HTTP 连接,核对 body reader 的释放状态
Deno 已完整消费请求体仍关闭连接,可能是 HTTP/1 reader 完成与释放状态未协调。2.9.6 包含处理,未读或处理中正文不属于同一复用保证。
Deno HTTP/1.1 服务已经完整读取请求体,响应仍包含 Connection: close,下一次请求只能重新连接,可以检查非预缓冲请求体的 reader 完成与连接释放状态。
这种故障在 2.8.3 至 2.9.5 的具体组合中有报告,2.8.2 是原对照的正常基线。触发与请求头、正文大小及最初读入的缓冲有关,不能把大约几百字节的观察写成所有环境固定的阈值。
先保留一个真正读到 EOF 的 handler:
Deno.serve({ port: 8000 }, async (request) => {
const body = await request.text();
return new Response(String(body.length));
});
只返回响应而没有消费正文,与这个案例不同。也要排除客户端或代理自己发送 Connection: close,以及响应长度、异常和中途断开导致的关闭。
旧 raw HTTP/1 路径把需要继续读取的正文当成不能复用的连接,即使 JavaScript 后来已经消费到结束。修补需要同时记录 reader 完成和释放连接:读到 EOF 与连接已经可以安全交给下一个请求,并不是随意可以互换的状态。未读或仍在处理的正文仍需关闭或按相应流程清理。
Deno 2.9.6 包含这条 keep-alive 修补。更新后,在隔离环境用同一个客户端连续发送两项请求,第一项带足够长且确实被 handler 消费的正文,第二项验证连接可以继续使用。使用 curl 时可在同一个命令中通过 --next 切换第二项请求,并用 verbose 输出检查实际连接复用;不要分别启动两个 curl 进程后用它们证明复用成功。
除了响应状态,还应检查 handler 得到的正文长度、内容以及第二项请求的结果。若数据被截掉但连接没有关闭,仍不是成功。使用 pipelining 的业务还要核对取消请求体后剩余数据被排空,不能让前一项正文变成下一项请求的开头。
这个故障属于服务端正文生命周期,不是客户端从空闲池拿到已关闭 socket 的竞态。对失败请求盲目增加重试,可能掩盖持续关闭,还可能重复写入业务数据。
修复不保证所有请求永久保持连接。未读正文、处理中止、客户端要求关闭、服务重启和代理策略仍可以正常结束连接;验收范围应围绕原来的“完整消费后仍关闭”场景。


