问题现象:再国产麒麟系统部署vue前端和java后端之后,由于前端业务问题需要使用nginx进行代理,在运行一段时间之后,接口突然提示响应失败,浏览器 Network 面板显示状态码却仍为200,但前端提示请求失败,部分较大的图片也存在只显示一半或加载失败的现象。通过查阅资料和一些官方文档得以解决。
一、运行环境与故障现象
故障环境如下:
- 操作系统:国产麒麟 Linux
- Web 服务器:Nginx 1.29.1
- 前端:Vue
- 后端 SpringBoot jar包
- 浏览器客户端:Chrome
- 访问方式:HTTPS
- 后端接口:通过 Nginx_proxy_pass 反向代理
现象/截图:
- 小数据量 JSON 可以正常返回。
- 大数据量 JSON 会在某个字段中间结束,缺少后续数组元素和闭合括号。
- 前端通常表现为JSON.parse失败、Axios 请求失败。
- 截断位置并不是固定的 200 KB,如接口完整响应约为 500 KB,而浏览器只接收到约 300 KB。
- 某些通过代理访问的大图片也可能只显示一部分。

返回数据不是合法 JSON,响应体在传输过程中提前结束。
二、为什么响应被截断,返回状态仍是200
HTTP 响应分为响应头和响应体。服务器通常先发送响应头:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
浏览器收到响应头后,就会在 Network 面板中显示200。如果后续传输响应体时连接中断,已经发出的状态码无法再改成 500,所以会出现“状态码 200,但 JSON 不完整”的情况。因此,200状态只能说明响应头成功发送,不能证明整个响应体已经完整到达浏览器。
查询诸多资料发现,反向代理场景至少包含两端独立连接:
浏览器 --HTTP/1.1--> Nginx --HTTP/1.0--> 后端服务
浏览器开发者工具只能直接看到第一段连接,即使状态正常,也不能证明 Nginx 请求后端时使用的是 HTTP/1.1。Nginx 完全可能从后端接收 HTTP/1.0 响应,再转换成 HTTP/1.1 chunked 响应发送给浏览器,实际使用的上游协议需要查看 Nginx 配置:
nginx -T | grep -n "proxy_http_version"
三、http1.0与http1.1的响应界定差异
HTTP 客户端必须知道响应体在哪里结束,常见方式有三种。
Content-Length
服务器提前计算响应体长度,客户端读取指定字节数。如果连接提前关闭,客户端可以判断还有数据未收到。
HTTP/1.1 chunked 分块传输
动态接口往往无法提前知道总长度,因此可以按块发送:Transfer-Encoding: chunked
每个块都带有长度,最后通过 0 长度块明确表示响应完成。连接是否关闭不再是“标记响应结束”的主要职责。
通过关闭连接表示响应结束
HTTP/1.0 不支持标准的 chunked 传输。动态响应没有 Content-Length 时,通常只能发送完数据后关闭连接,以 EOF 表示响应结束,问题在于“正常发送完成后关闭连接”、“响应只发送一部分,连接异常断开”。
在仅依赖连接关闭定界时非常相似。如果后端应用服务器、连接池或代理层在 HTTP/1.0 模式下提前关闭连接,Nginx可能已经向浏览器返回了200,最终浏览器只能得到一个被截断的响应体。
四、为什么小响应正常,大响应容易失败
研究得出小响应可能一次写操作就能完成:
写JSON -> 写Websocket -> 关闭连接
大响应通常需要多次写入,并经过更多缓冲和调度:
生成一部分 -> 写入
生成下一部分 -> 再写入
多次循环 -> 完成
响应越大,越容易触发连接关闭、输出流、缓冲区切换或连接池中的兼容问题。因此数据量看起来像触发条件,但并不存在一个固定的“返回数据大小限制”。
五、测试流程
1.在本地使用windows客户端curl 复现
在 Chrome Network 中右键请求,选择“复制 -> 以 cURL 格式复制(CMD)”,需要注意Windows CMD 不能使用 Linux 风格的单引号,参数必须使用英文半角双引号。若内网证书无法检查吊销状态,Windows curl 可能报:
curl: (35) schannel: InitializeSecurityContext failed: 0x80092012
诊断是可以添加:
-k --ssl-no-revoke
测试关闭长连接:
curl.exe -k --ssl-no-revoke -v --http1.1 "https://x.x.x.x:xxxx/baseApi/task/query" -H "Accept: application/json" -H "Content-Type: application/json" -H "Connection: close" --data-raw "{\"id\":29}" --output result-close.json
echo curl退出码=%errorlevel%
dir result-close.json
测试保持长连接:
curl.exe -k --ssl-no-revoke -v --http1.1 "https://x.x.x.x:xxxx/baseApi/task/query" -H "Accept: application/json" -H "Content-Type: application/json" -H "Connection: keep-alive" --data-raw "{\"id\":29}" --output result-keepalive.json
echo curl退出码=%errorlevel%
dir result-keepalive.json
验证返回的 JSON 是否完整:
powershell -NoProfile -Command "(Get-Item .\result-close.json).Length"
powershell -NoProfile -Command "Get-Content -Raw .\result-close.json | ConvertFrom-Json | Out-Null; Write-Host 'JSON完整'"
常见 curl 结果:
退出码 0:传输正常完成
退出码 18:实际收到的数据少于协议声明的数据
退出码 35:TLS握手或证书问题
退出码 56:连接被重置或接收失败
2.在麒麟服务器本机测试 Nginx
我的服务器注记依赖Host/SNI,测试命令如下:
curl -k -v --http1.1 \
--resolve x.x.x.x:xxxx:127.0.0.1 \
'https://x.x.x.x:xxxx/baseApi/task/query' \
-H 'Content-Type: application/json' \
-H 'Connection: close' \
--data-raw '{"id":29}' \
-o /tmp/result-nginx.json
echo "curl exit=$?"
wc -c /tmp/result-nginx.json
tail -c 300 /tmp/result-nginx.json
jq可以验证 JSON:
jq empty /tmp/result-nginx.json && echo 'JSON完整'
3.绕过 Nginx,直接对比后端 HTTP/1.0 和 HTTP/1.1
这是最能验证根因的测试。将地址和端口替换成真实后端地址:
curl -v --http1.0 \
'http://x.x.x.x:xxxx/task/query' \
-H 'Content-Type: application/json' \
--data-raw '{"id":29}' \
-o /tmp/backend-http10.json
curl -v --http1.1 \
'http://x.x.x.x:xxxx/task/query' \
-H 'Content-Type: application/json' \
--data-raw '{"id":29}' \
-o /tmp/backend-http11.json
wc -c /tmp/backend-http10.json /tmp/backend-http11.json
jq empty /tmp/backend-http10.json
jq empty /tmp/backend-http11.json
如果 HTTP/1.0 结果被截断而 HTTP/1.1 完整,就直接证明问题发生在 Nginx与后端的协议或连接处理层。
5. 检查麒麟系统上的 Nginx 日志和运行环境
复现时观察:
tail -f /var/log/nginx/error.log
journalctl -u nginx -f
麒麟系统也必须遵循相同的 HTTP 规范。操作系统本身一般不会主动限制 返回数据 响应,但发行版内核、OpenSSL版本、Nginx编译参数、安全模块和防火墙会影响具体表现,因此排查时注意这些环境信息。
六、最终nginx配置
1.普通 HTTP/JSON 接口配置:
location /baseApi/ {
proxy_pass http://x.x.x.x:xxxx;
# Nginx 到后端使用 HTTP/1.1
proxy_http_version 1.1;
# 不将客户端的逐跳 Connection 头传递给后端
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 30s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
重新加载:
nginx -t
systemctl reload nginx
2.WebSocket 配置
需要显式升级:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
七、错误修改和结论
找问题的时候改过"client_max_body_size、client_body_buffer_size",但是它们主要控制客户端上传的请求体,而本次问题发生在服务器返回的响应体,所以改了没什么用,“proxy_buffer_size”和“proxy_buffers”,也不是接口响应的最大大小。响应超过内存缓冲后,Nginx通常会使用代理临时文件;只有临时目录权限、磁盘空间或 I/O 异常时,才可能造成响应失败。
本次问题的表面现象是“大 JSON 被截断但状态码仍为 200”,真正需要检查的是反向代理两侧的协议和响应边界。
最终修复配置为:
proxy_http_version 1.1;
proxy_set_header Connection "";
修复后,大数据 JSON 可以完整返回,浏览器不再报请求失败。关于这次排查得到的几个关键经验:
- 返回状态200不代表响应体完整。
- 浏览器显示 HTTP/1.1,不代表 Nginx 到后端也是 HTTP/1.1。
- HTTP/1.0 没有小响应大小限制,问题在于当前环境中的响应定界和连接处理兼容性。
- 大响应更容易暴露多次写入、连接关闭和代理协议问题。
- 最有效的定位方法是分别测试浏览器、Nginx本机和后端直连,并对比 HTTP/1.0 与 HTTP/1.1。
参考资料:







Comments | 1 条评论
太胖了!