Node 接口已经逐段返回,浏览器却等到最后才看到全部内容,问题可能出在反向代理。应用层的流式响应,需要经过代理以后仍然按过程交付。
Nginx 同时负责 HTTPS 和转发时,可以分两层检查:直连 Node 有没有逐段输出,再看代理后的行为。
一个每半秒发一条的接口
创建空目录,保存 server.mjs:
import http from "node:http";
http
.createServer((req, res) => {
if (req.url !== "/stream") {
res.writeHead(404);
return res.end();
}
res.writeHead(200, {
"Content-Type": "application/x-ndjson; charset=utf-8",
"Cache-Control": "no-store",
});
res.flushHeaders();
let index = 0;
const timer = setInterval(() => {
res.write(JSON.stringify({ index, time: Date.now() }) + "\n");
if (++index === 3) {
clearInterval(timer);
res.end();
}
}, 500);
res.on("close", () => clearInterval(timer));
})
.listen(3000, "0.0.0.0");
node server.mjs 运行后,在另一个终端执行 curl -N http://127.0.0.1:3000/stream。-N 关闭 curl 自身的输出缓冲,避免客户端工具把现象藏起来。应看到三条 JSON 逐步出现。
HTTP 数据块和 JSON 行不是一一对应的关系。这个命令适合人工观察,真正消费数据时仍要按换行解析。
配置代理和本地 HTTPS
创建 Dockerfile:
FROM node:22.14.0-bookworm-slim
WORKDIR /app
COPY --chown=node:node server.mjs ./
USER node
EXPOSE 3000
CMD ["node", "server.mjs"]
创建 nginx.conf:
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /etc/nginx/certs/cert.pem;
ssl_certificate_key /etc/nginx/certs/key.pem;
location / {
proxy_pass http://app:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_buffering off;
proxy_read_timeout 30s;
gzip off;
}
}
proxy_pass 的 app 是下面 Compose 的服务名。Host 和 X-Forwarded-Proto 把原始请求信息传给应用;应用是否信任这些头,仍然要按实际代理拓扑配置。
proxy_buffering off 关闭这个 location 的代理响应缓冲。gzip 在这个小例子里关闭,便于减少观察变量,不代表所有生产流式接口都必须关闭压缩。
proxy_read_timeout 约束两次上游读取之间的等待,不是整个响应必须在 30 秒内结束。长时间不发送任何内容的接口,可能需要心跳和合适的超时。
创建 certs 目录。在有 OpenSSL 的终端生成仅用于本地的自签证书:
openssl req -x509 -newkey rsa:2048 -nodes -days 30 -keyout certs/key.pem -out certs/cert.pem -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
私钥不放进 Git。创建 compose.yaml:
services:
app:
build: .
ports:
- "127.0.0.1:4312:3000"
proxy:
image: nginx:1.26.3-bookworm
depends_on:
- app
ports:
- "127.0.0.1:4313:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./certs:/etc/nginx/certs:ro
直连和代理分开观察
docker compose config --quiet
docker compose up --build -d
docker compose exec proxy nginx -t
curl -N http://127.0.0.1:4312/stream
curl -k -N https://localhost:4313/stream
docker compose logs app proxy
-k 只是本地自签证书演示跳过证书校验,正式访问不应照搬。先确认 nginx -t 通过,再比较两条 curl 命令是否都是逐条输出。
目前只完成直连 Node 的流式检查和 Compose 配置检查;本机 Docker 后端不可用,Nginx 容器、TLS 和代理后分段到达的行为还需要在可运行的环境验证。
代理缓冲的官方说明见 Nginx proxy_buffering。如果 Nginx 后面还有 CDN、网关,它们也可能影响响应。关闭一层缓冲不能替其他层作保证。