Nginx 关闭请求缓冲后,上游仍收到 Content-Length
关闭请求缓冲不保留客户端分块编码。小正文完整预读后可转为Content-Length;按正文完整性和首段到达验收,区分请求方向、版本及业务重试。
客户端发送 Transfer-Encoding: chunked 的请求,Nginx 已设置 proxy_request_buffering off 和 proxy_http_version 1.1,上游却看到 Content-Length: 2。如果这段两字节正文在代理开始构造上游请求前就已全部读到,这种转换不表示正文丢失。
关闭请求缓冲控制的是接收和转发请求体的方式,不承诺保留客户端连接上的分块编码。小正文已经完整预读时,长度已经知道;仍在读取未知长度正文时,处理路径不同。Nginx 1.30.5 的正式代码仍区分这两种状态。
先核对正文,而不是要求同一传输头
在自己控制的接收端,分别记录客户端发送的业务字节,以及上游 HTTP 栈解码后的请求体。使用无敏感数据的样本,检查字节数、内容和业务解析结果;必要时比较原始正文哈希,记录方法和实际 HTTP 版本。
下面只展示已经确认正文为 aa 的两种等价表达,不是让你手工拼接请求头:
客户端连接:Transfer-Encoding: chunked
解码后的正文:aa
上游连接:Content-Length: 2
正文:aa
chunk 长度字段、分隔换行和终止块属于传输编码,不是业务正文的一部分。不能要求接收应用按客户端原来的 chunk 边界识别消息;代理或 HTTP 栈可能去掉或重新编码这些边界。业务需要分条记录时,应在正文格式中定义边界,例如应用已经约定的行分隔或长度字段。
如果确实发现正文少了换行,也检查数据产生端。curl 的普通文件数据选项会处理某些字节,见 按原字节发送文件。只看到上游 Content-Length 与源文件不同,不能断定 Nginx 丢了内容。
已预读完整与仍在读正文的区别
普通代理路径构造上游请求时,如果客户端使用 chunked 且 Nginx 仍在读取正文,会按未知长度的内部 chunked 路径处理。若正文已完整预读,便可使用已知长度,不必保留原来的 Transfer-Encoding。
一次很小的请求可能连同请求头一起到达并完整读入。把测试内容改大,或让客户端分时段发送,可能走到不同状态;这解释处理分支,不保证某个大小阈值或每次网络时序都相同。
所以“有正文反而没有 chunked”不是充分的错误证据。反过来,上游看见 chunked 也不证明业务字节一定完整,仍需核对接收与处理结果。另一个上传请求返回 422 时,应根据那个请求的正文与业务约束单独排查。
确实需要流式上传时检查请求方向
目标是让上游边接收边处理,而非等待完整上传时,在已有对应代理块中核对:
proxy_http_version 1.1;
proxy_request_buffering off;
这是配置片段,合入原 location,保留认证、体积限制、超时和其他代理设置。请求缓冲指令自 1.7.11 起提供;客户端使用 HTTP/1.1 chunked 时,上游代理协议若不支持适当传输,仍会缓冲。当前正式版本也支持代理 HTTP/2,需要相应版本、模块和上游支持,不能在旧实例上直接套用。
Nginx 从 1.29.7 起默认代理 HTTP/1.1,更早的默认是 HTTP/1.0。因此检查已加载配置和实际版本,不能用当前默认解释所有历史实例。即便满足流式条件,也不保证传输头或每个 chunk 原样穿过代理;上游框架本身还必须按流消费,不能先收完整再解析。
proxy_buffering 控制上游响应缓冲,chunked_transfer_encoding 也不能当作保持客户端请求分块的开关。不要手工强塞 Transfer-Encoding 或 Content-Length,尤其不要构造同时声明两者、或与实际正文长度冲突的请求。
验收首段到达和最终完整性
保存原配置,在本机以实际部署版本检查后按原流程加载。用受控的小正文及增量上传分别观察上游何时收到首段、何时完成,检查完整字节与业务结果,并确认慢上传仍受原有资源和时间限制。
请求体开始发送给某个上游后,关闭缓冲的请求不能假设还能安全切换到下一个上游重放。失败重试须核对业务是否已产生部分结果及幂等规则,不能为比较传输头重复提交真实操作。
如果内容与处理均正确,仅编码方式改变,无需为恢复一个头改 Nginx 源码。撤回配置改动也不能撤销上游已经完成的业务;恢复原缓冲和协议设置后,仍要检查上传时序与完整结果。


