DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Caddy 2.11.6 的 POST 流 60 秒断开

Caddy 2.11.6 代理 HTTP/1.1 非空 POST 流时,请求体 deadline 可能在读完后残留,令持续有心跳的响应约 60 秒中断。2.11.7 已修复。

SSE 已经正常发送心跳,经过 Caddy 代理的连接却在约 60 秒后断开,客户端提示 transfer closed with outstanding read data remaining,代理日志出现 context canceled。如果使用 Caddy 2.11.6、HTTP/1.1,且开启流的 POST 带有非空请求体,应检查请求体读取的 idle deadline 是否在正文读完后仍然保留。

这个缺陷已有正式 2.11.7 修复。它不等于所有 SSE 超时:GET、空请求体 POST、其他 HTTP 协议以及其他中间代理,都需要按实际请求路径核对。

心跳持续到达,也可能被请求体 deadline 取消

2.11.6 的 read_body_idle 默认时间为一分钟,用来限制客户端上传正文时一次读取停滞的时长。受影响实现读取完请求体后,没有清掉相应 deadline。

HTTP/1.1 连接之后用于发现客户端断开的后台读取,会碰到这个残留 deadline。请求上下文被取消时,响应虽然正在持续发送,也会被中止。因此给 SSE 增加心跳,或反复修改 flush_interval,不能处理已经完成的请求体留下的 deadline。

先记录实际 caddy version,再核对请求是否确实为 HTTP/1.1、是否有非空正文、断开时间与正文读完的时间是否相符。客户端自己的 --max-time、负载均衡器和上游超时也应分别记录,避免把任何一分钟边界都归到同一缺陷。

用同一条受控流比较代理前后

下面的本机地址是示例,必须替换为已经存在、允许测试且没有业务写入副作用的流式诊断端点。正文及认证要求按那个端点的契约填写,不要向创建订单等业务 POST 发探测请求:

curl -N --http1.1 --max-time 90 \
  -H 'Content-Type: application/json' \
  --data '{}' \
  http://127.0.0.1:8080/events-check

用相同身份、正文和协议,比较获准测试环境中的直连与代理路径。确认心跳是否持续到达、在哪一层断开,以及断开时客户端和代理各自记录的错误。测试远程端点时保留原有 HTTPS 和认证配置,不要为比较直接开放受保护的上游。

这里的 90 秒是客户端主动结束测试的上限。流正常持续超过原来的 60 秒后,curl 最终因自己的上限返回超时,与代理提前关闭连接是不同结果。

升级包含修复的正式构建

确认符合这组条件后,按现有部署流程升级到包含修复的 2.11.7。自定义插件构建也要保留模块清单,核对新构建与配置兼容性,不能只更改镜像标签后假定运行中的二进制已经更新。

修改前保留原配置和可恢复的应用镜像或二进制。更新后用原来的非空 POST 流验收:连接跨过原 60 秒边界,事件仍完整到达,日志不再因这份残留读取 deadline 取消请求。

若暂时不能升级,单独调整 read_body_idle 属于有代价的临时例外:关闭它会同时弱化该服务器范围内的慢上传限制,不能当作某条 SSE 路由独享的无影响开关。优先使用正式修复;不要为了断流一次性关闭全部超时。

最后检查正常上传、访问控制和其他流式请求仍符合原策略。若升级后仍断开,应按实际错误继续查其他链路,不能把 2.11.7 当作所有 SSE 故障的答案。