SSE 流式请求避免 nginx 缓冲导致块式输出的解决办法

目录
[隐藏]

在前端以 SSE(Server Sent Event) 调用大模型 API 请求时,正常应该是流畅的按字符响应,但却出现了断断续续按块式输出的现象,使得体验较差。

这主要是因为 Nginx 默认会启用代理缓冲 proxy_buffering,从而导致 SSE 数据被缓存并按块返回。可以通过修改 Nginx 配置禁用缓冲来解决。

1 Nginx 禁用 proxy_buffering 配置示例

以下是一个简单的 Nginx 配置示例,它禁用了代理缓冲:

server {
    listen 443 ssl http2;
    server_name example.com;

    # 处理静态文件的路径
    location /static/ {
        alias /home/lzw.me/test-sse/static/;
    }

    # 正常 API 请求转发到后端
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $http_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;
    }

    # 转发 SSE 请求,匹配以 /sse/ 开头的路径
    location ~ ^/sse/ {
        # 后端服务器地址
        proxy_pass http://127.0.0.1:8000;
        # proxy_pass http://backend_upstream;

        # 关闭代理缓冲
        proxy_buffering off;
        # 关闭代理缓存
        proxy_cache off;
        # 设置代理的响应头部,保持传输编码为 chunked
        proxy_set_header X-Accel-Buffering no;

        # 设置 HTTP 版本为 HTTP/1.1
        proxy_http_version 1.1;
        # 保持连接活性,不发送 `Connection: close` 连接关闭 header 信息
        proxy_set_header Connection '';

        # 设置代理读取服务器响应的超时时间,避免 SSE 长连接被断开
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
        # 设置客户端连接的超时时间
        proxy_connect_timeout 1h;

        # 转发访问端使用的 Host 头信息
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # 禁用客户端缓存
        add_header Cache-Control no-cache;
        # 禁用 Nginx 的响应缓冲。也可以在程序代码中设置此 header,例如(Java示例):
        # res.setHeader('X-Accel-Buffering', 'no');
        add_header X-Accel-Buffering "no";
        # 启用分块传输编码
        chunked_transfer_encoding on;

        # 防止 gzip 压缩,避免 chunked 被打包
        gzip off;

        # 允许 CORS 跨域(CORS 跨域为可选,视实际情况而定)
        add_header 'Access-Control-Allow-Origin' '*' always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Origin,Authorization,Accept,X-Requested-With' always;

        # 支持 CORS 预检请求,返回 204
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'Origin,Authorization,Accept,X-Requested-With';
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            add_header 'Content-Length' 0;
            return 204;
        }
    }
}

2 APISIX 禁用 proxy_buffering 配置示例

APISIX 是基于 Nginx 的 API 网关,可以通过配置 proxy-rewrite 插件的 disable_buffering 参数来达到同样的效果。示例配置如下:

{
    "uri": "/sse",
    "plugins": {
        "proxy-buffering": {
            "disable_proxy_buffering": true
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "your-backend-service:80": 1
        }
    }
}

此外也可以通过 response-rewrite 插件添加 X-Accel-Buffering: no 头信息来实现同样的效果:

{
  "disable": false,
  "headers": {
    "X-Accel-Buffering": "no"
  }
}

3 相关参考