1. 异常现象
后端接口返回一个text/event-stream协议的stream流用于持续响应回话内容,使用WebClient访问目标地址,目标地址响应会话内容例如是 “你真帅”,将这个结果进行流转换成为ChatVo自定义封装对象,再使用Flux.just将对象转成json响应一个流给前端
@PostMapping(value = "/appChatMessage", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux appChatMessage(@RequestBody MessageVo messageVo, HttpServletRequest request, HttpServletResponse response) {
return openApiService.appChatMessageStream(messageVo, request, response);
}
WebClient webClient = WebClient.create("http://xxxx.com/stream");
webClient.post()
.uri(String.format(MESSAGE_URL, params.get(Constant.CHAT_ID)))
.headers(httpHeaders -> {
httpHeaders.put(HttpHeaders.ACCEPT, Lists.newArrayList(MediaType.TEXT_EVENT_STREAM_VALUE));
httpHeaders.put("AUTHORIZATION", Lists.newArrayList((String) appInfoVo.getConfig().get(Constant.API_KEY)));
httpHeaders.put("accept", Lists.newArrayList("application/json"));
httpHeaders.put("Content-Type", Lists.newArrayList("application/json"));
})
.body(BodyInserters.fromObject(JSON.toJSONString(bodyMap)))
.retrieve()
.bodyToFlux(String.class)
.flatMap(res -> {
try {
ChatVo chatVo = JSON.parseObject(res, ChatVo.class);
return Flux.just(JSON.toJSONString(chatVo));
} catch (Exception e) {
}
return Flux.empty();
})
.timeout(Duration.ofSeconds(60 * 5));
前端解析流应该是一个比较独立的json才对,诡异的来了但是解析出来却是一个流中粘黏拼接堆叠了大量的内容,而且流的结尾还有几率会被截断导致json不完整,前端解析不完整的内容大量出现 ??? 的乱码。
2. 解决方式
在处理 text/event-stream
时,启用 Nginx 的 proxy_buffering
可能会导致响应粘连和 JSON 被截断的问题。坏处具体来说有:
响应粘连:如果 Nginx 在缓冲过程中将多个流stream事件合并,客户端接收的事件流可能不是分开的,常常发生所有事件都被缓冲的情况,导致SSE接口失去其特有的能力。
数据截断:当流中的数据被分块传输时特别是steam想用json内容,Nginx 可能在传输中断或数据不完整的情况下缓存数据,从而使得客户端收到不完整的 JSON 对象导致解析失败。
这两个指令的作用如下,总之,这两个指令主要用于确保数据的实时性和完整性,特别是在处理流式响应时。
proxy_buffering off;
禁用 Nginx 的反向代理缓冲功能。这样,Nginx 会将后端服务器的响应直接传递给客户端,不会在内存中进行缓存。这在处理实时流数据(如text/event-stream
)时非常有用,可以避免响应粘连和数据截断的问题。proxy_cache off;
禁用 Nginx 的反向代理缓存功能。这意味着 Nginx 不会存储后端服务器的响应,也不会对后续的相同请求提供缓存数据。这样做可以确保客户端每次都能获取到最新的内容,适用于需要实时更新的数据场景。
location /apis {
.....
proxy_buffering off;
proxy_cache off;
}
变更后可以看到响应不再粘连堆叠,每个流都是一个独立的json非常完整也不再被截断出现解析错误和乱码。