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不完整,前端解析不完整的内容大量出现 ??? 的乱码。

kRJnilE6xL.jpg

2. 解决方式

在处理 text/event-stream 时,启用 Nginx 的 proxy_buffering 可能会导致响应粘连和 JSON 被截断的问题。坏处具体来说有:

  • 响应粘连:如果 Nginx 在缓冲过程中将多个流stream事件合并,客户端接收的事件流可能不是分开的,常常发生所有事件都被缓冲的情况,导致SSE接口失去其特有的能力。

  • 数据截断:当流中的数据被分块传输时特别是steam想用json内容,Nginx 可能在传输中断或数据不完整的情况下缓存数据,从而使得客户端收到不完整的 JSON 对象导致解析失败。

这两个指令的作用如下,总之,这两个指令主要用于确保数据的实时性和完整性,特别是在处理流式响应时。

  1. proxy_buffering off;禁用 Nginx 的反向代理缓冲功能。这样,Nginx 会将后端服务器的响应直接传递给客户端,不会在内存中进行缓存。这在处理实时流数据(如 text/event-stream)时非常有用,可以避免响应粘连和数据截断的问题。

  2. proxy_cache off;禁用 Nginx 的反向代理缓存功能。这意味着 Nginx 不会存储后端服务器的响应,也不会对后续的相同请求提供缓存数据。这样做可以确保客户端每次都能获取到最新的内容,适用于需要实时更新的数据场景。

location /apis {
     .....
   
     proxy_buffering off;
     proxy_cache off;
}

变更后可以看到响应不再粘连堆叠,每个流都是一个独立的json非常完整也不再被截断出现解析错误和乱码。

EvXqYUAvnc.jpg