SseEmitter + WebClient + Flux实现SSE事件流推送

摘要

本文介绍了如何实现SSE事件流的接收和处理,通过使用Spring自身非常兼容的三大组件来实现,让我们开始。

三个核心组件

SseEmitter + WebClient + Flux

  • SseEmitter

在服务器端事件(SSE, Server-Sent Events)场景中,SSE的响应格式通常是text/event-stream,并且服务器会持续向客户端发送事件流,而不是一次性返回所有结果。SseEmitter是Spring提供的用于实现SSE的核心类,它可以保持与客户端的长连接,并逐步发送数据,确保每条数据都能及时推送到客户端。

  • WebClient

WebClient是Spring WebFlux提供的响应式HTTP客户端,它是响应式、非阻塞的,支持异步和流式处理,非常适合SSE场景。使用WebClient + SseEmitter实现服务端推送。可以充分利用Spring的响应式编程能力,实现高效、稳定的流式通信。适用于AI聊天、实时日志、数据流等场景

  • Flux

Flux是Reactor库中的一个核心概念,用于处理异步的、基于事件的响应式编程模型。Flux表示一个异步的序列数据流,可以是零个或多个数据项,甚至可以是无限流,并且可以处理各种事件(如数据项、错误、完成等)。Flux的主要作用是处理多个数据项的连续传输,这与SSE场景非常契合,因为SSE就是典型的服务器向客户端发送连续事件流。

逻辑解析

  • 构建Web请求

通过WebClient发送http请求,并设置请求头和请求体并将响应体解析为Flux,表示一个可订阅的字符串流。

  • 获取响应流

通过.retrieve()方法获取响应,并通过.bodyToFlux(String.class)将响应体转换为Flux。这表示响应体将被解析为一个字符串序列流。

  • 订阅流

通过.subscribe()方法订阅这个流,处理流中每个数据项。每当服务器发送一个新的数据项(例如SSE事件),subscribe方法中的回调函数就会被触发,处理这个数据项。

data -> {}:处理流中的每个数据项,每当服务器发送一个新的数据项,这个回调函数就会被触发。

error -> {}:处理流中的错误事件。

() -> {}:处理流的完成事件,当服务器关闭连接时触发。

  • SseEmitter超时设置

Spring Boot2.2.0及之后,默认超时时间是60秒。

可通过new SseEmitter(Long timeout)设置

  • 实时推送数据

订阅Flux,在每次收到数据时都实时推送给客户端。

SseEmitter.send(...)

  • 异步执行请求

使用线程池异步执行HTTP请求,避免阻塞主线程。

executorService.execute(() -> {...})

  • 异常处理与连接关闭

无论成功还是失败,最终都要终止连接,确保资源释放。

emitter.complete()

emitter.completeWithError(...)

代码示例

事件流示例:

kotlin 复制代码
-- 返回示例
已连接到 http://xxx
data: {"字段名1": "内容", "字段名2": "内容", "字段名3": "内容"}

data: {"字段名1": "内容", "字段名2": "内容", "字段名3": "内容"}

data: {"字段名1": "内容", "字段名2": "内容", "字段名3": "内容"}
已断开连接 http://xxx

导入依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

核心代码:

java 复制代码
package org.coffeebeans.sse;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import reactor.core.publisher.Flux;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;

/**
 * <li>ClassName: SseService </li>
 * <li>Author: OakWang </li>
 */
@Service
@Slf4j
public class SseService {

    private final ExecutorService executorService = Executors.newCachedThreadPool();
    private final WebClient webClient = WebClient.builder().baseUrl("http://xxx").build();

    public SseEmitter test() {
        // 设置请求头
        String authorization = "Bearer xxx";

        // 初始化参数
        Map<String, Object> params = new HashMap<>();
        params.put("name", "xxx");
        // 转换为JSON字符串
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonRequest;
        try {
            jsonRequest = objectMapper.writeValueAsString(params);
        } catch (JsonProcessingException e) {
            log.error("JSON转换失败:", e.getMessage());
            throw new RuntimeException("JSON 转换失败");
        }

        SseEmitter emitter = new SseEmitter(60000L); // 设置连接超时时间为60秒
        // 使用线程池异步发送数据
        executorService.execute(() -> {
            try {
                // 构造请求参数
                Flux<String> flux = webClient.post()
                        .uri("/xxx")
                        .header("Authorization", authorization)
                        .header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
                        .bodyValue(jsonRequest) // 构造 JSON 请求体
                        .retrieve() // 获取响应
                        .bodyToFlux(String.class); // 将响应体转换为 Flux<String>,解析为一个字符串序列流

                flux.subscribe(
                        data -> { // 处理每个数据项
                            try {
                                emitter.send(SseEmitter.event().data(data)); // 实时推送
                            } catch (IOException e) {
                                log.error("推送数据失败", e);
                                emitter.completeWithError(e);
                            }
                        },
                        error -> { // 处理错误
                            try {
                                emitter.send(SseEmitter.event().data(error.getMessage()));
                            } catch (IOException e) {
                                log.error("推送数据失败", e);
                                throw new RuntimeException(e);
                            } finally {
                                emitter.completeWithError(error);
                            }
                        },
                        () -> { // 处理完成事件
                            emitter.complete(); // 数据流结束
                        }
                );
            } catch (Exception e) {
                log.error("初始化流式请求失败", e);
                try {
                    emitter.send(SseEmitter.event().data(e.getMessage()));
                } catch (IOException ex) {
                    emitter.completeWithError(e);
                }
                emitter.completeWithError(e);
            }
        });
        return emitter;
    }

}

总结

以上我们了解了SSE响应流的处理方式,通过三个核心组件:SseEmitter、WebClient和Flux共同实现高效的服务器端事件(SSE)实时推送功能。

关注公众号:咖啡Beans

在这里,我们专注于软件技术的交流与成长,分享开发心得与笔记,涵盖编程、AI、资讯、面试等多个领域。无论是前沿科技的探索,还是实用技巧的总结,我们都致力于为大家呈现有价值的内容。期待与你共同进步,开启技术之旅。

相关推荐
你三大爷2 小时前
Safepoint的秘密探寻
java·后端
努力也学不会java3 小时前
【Java并发】揭秘Lock体系 -- condition等待通知机制
java·开发语言·人工智能·机器学习·juc·condition
我需要打球3 小时前
SpringMVC的执行流程
java·servlet
瑞士卷@3 小时前
JDBC进阶之连接池的配置(Druid与HikariCP)
java·开发语言·数据库
xiaopengbc4 小时前
泛型在Java集合框架中的应用有哪些?
java·开发语言·python
沐浴露z4 小时前
一篇文章入门RabbitMQ:基本概念与Java使用
java·分布式·rabbitmq
失散134 小时前
分布式专题——24 Kafka功能扩展
java·分布式·云原生·架构·kafka
zhangxuyu11185 小时前
全栈工程师项目练习记录
java·spring boot
讓丄帝愛伱5 小时前
阿里开源 Java 诊断神器Arthas
java·linux·开发语言·开源