最近公司准备做个类似 GPT 一样的聊天功能,使用 SSE 来实现。 写了个 demo ,我在本机测试没问题,上了测试环境发现输出的内容都是等待后一次性输出到前端,并不是打字机的效果。
服务端代码如下:
@GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter c() {
SseEmitter sseEmitter = new SseEmitter();

log.error("xxx start");
//调用流式会话服务
new Thread(() -> streamChatCompletion(sseEmitter)).start();
log.error("xxx end");
//及时返回 SseEmitter 对象
return sseEmitter;
}

public void streamChatCompletion(SseEmitter emitter) {
try {
for (int i = 0; i < 3; i++) {
String o = "test" + i;
emitter.send(o);
Thread.sleep(1000); // 每秒发送一次
}
emitter.send(SseEmitter.event().name(" stop").data(""));
emitter.complete(); // 完成发送
} catch (IOException | InterruptedException e) {
emitter.completeWithError(e); // 发送错误
}
}

抓请求头 和 返回头吧

nginx 没配置吧,好像有个什么缓存的要关了

被网关缓存了,每一层网关都要检查

哈哈,上面 nginx 代理也有 sse 相关的配置的

public void demo( HttpServletResponse httpServletResponse) throws IOException {
httpServletResponse.setContentType("text/event-stream");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setHeader("Cache-Control", "no-cache");
ServletOutputStream outputStream = httpServletResponse.getOutputStream();
while{
outputStream.write(("data: " + 字符串 + "\n\n").getBytes());
}
outputStream.write(("data: done\n\n").getBytes());
outputStream.flush();
} 查查 sse 的教程 www.ruanyifeng.com/blog/2017/05/server-sent_events.html

大概率 HTTP 版本低于 HTTP/1.1 。

Response Headers:
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: null
Access-Control-Expose-Headers: permission, username, eagleeye-traceid
Connection: keep-alive
Content-Type: text/event-stream;charset=UTF-8
Date: Wed, 26 Feb 2025 02:00:08 GMT
Keep-Alive: timeout=4
Proxy-Connection: keep-alive
Server: f6car
Set-Cookie: romaSESSIONID=de1f56ef-5301-412a-8537-abe613dd1dc1; Path=/roma; HttpOnly
Vary: Origin
X-Application-Context: xx:test:8888

Request Headers:
GET /xx/sse/connect HTTP/1.1
Accept: text/event-stream
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: no-cache
DNT: 1
Host: report-pre.f6car.com
Origin: null
Proxy-Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36

GET /xx/sse/connect HTTP/1.1

Ngnix

proxy_ssl_verify off;
proxy_ssl_session_reuse off;
proxy_buffering off; # 禁用缓存

谢谢大佬,我试试

大概率 nginx 的配置,网络链路上经过的中间件都排查下看看

nginx 的 proxy_buffering 配置,或者你响应头带一个 X-Accel-Buffering: no 也可以,详见 nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering

proxy_http_version 1.1;
# 设置 SSE 连接的超时时间,不设置默认是 2.5 分钟
proxy_read_timeout 86400s;
# 关闭缓冲
proxy_buffering off;
# 关闭代理缓存
proxy_cache off;

sse 这一个 proxy_buffering off;就好了,其它默认

谢谢大佬,加上这个响应头就可以了。

#12 ,响应头添加 X-Accel-Buffering: no ,这个方法更好

后端返回的流式输出效果不一定是平滑的,需要前端在做一层过滤来输出,这样才能达到平滑输出的效果。

我也做了这个需求,我在后端代码加多了 3 个响应头:
Content-Type: text/event-stream
Cache-Control: no-cache
X-Accel-Buffering: no

#18 这样更好,架构复杂了,ddos waf gateway lb ,所有环节添加 proxy_buffering 很不灵活

X-Accel-Buffering: no 就行。别去瞎改 nginx 配置。