java后端http接口流式输出到前端的方法示例

 更新时间:2025年09月19日 10:37:39   作者:abcnull  
这篇文章主要介绍了java后端http接口流式输出到前端的相关资料,对比SseEmitter与WebFlux两种实现方式,通过实例代码详细分析了其适用场景及优劣,需要的朋友可以参考下

前置

解释:

Server-Sent Events:服务器发送事件,是一种基于 HTTP 的轻量级协议,允许服务器主动向客户端推送文本数据(如 JSON、纯文本等)

特点:

  • 单向通信:仅服务器 → 客户端方向
  • 基于 HTTP/HTTPS:无需特殊协议
  • 自动重连:浏览器内置支持断线重连
  • 简单易用:前端直接使用 EventSource API

流程:

前端 (Vue)       → 发起SSE请求 →   Spring Boot (Controller)
  ↓                                   ↓
(EventSource)    ← 流式数据 ←   WebClient → 大模型API (流式HTTP)

后端流式输出

使用 springboot SseEmitter

传统项目小范围流式推送 → SseEmitter(改动成本低)

轻量级 SSE 推送、兼容旧 MVC 项目

每个请求占用一个线程,高并发时资源消耗大

@RestController
public class StreamingController {
    @GetMapping("/stream")
    public SseEmitter streamText() {
        SseEmitter emitter = new SseEmitter(60_000L); // 设置超时时间(毫秒)

        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.execute(() -> {
            try {
                for (int i = 1; i <= 10; i++) {
                    String data = "这是第 " + i + " 段内容\n";
                    emitter.send(SseEmitter.event().name("message").data(data));
                    Thread.sleep(1000); // 模拟延迟
                }
                emitter.complete(); // 结束流
            } catch (IOException | InterruptedException e) {
                emitter.completeWithError(e);
            } finally {
                executor.shutdown();
            }
        });

        return emitter;
    }
}

代码内部直接承接大模型 api 调用,然后流式输出到前端

@RestController
public class ModelStreamController {
    // 创建线程池
    private final ExecutorService executor = Executors.newCachedThreadPool();
    @GetMapping("/model-stream")
    public SseEmitter streamModel() {
        // 创建 SSE 发射器(60秒超时)
        SseEmitter emitter = new SseEmitter(60_000L);
        
        // 提交任务到线程池
        executor.execute(() -> {
            try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
                // 1. 创建到大模型API的请求
                HttpGet request = new HttpGet("https://api.open-model.com/stream");
                // 2. 执行请求
                HttpResponse response = httpClient.execute(request);
                // 3. 检查响应状态
                if (response.getStatusLine().getStatusCode() != 200) {
                    throw new RuntimeException("API error: " + 
                            response.getStatusLine().getStatusCode());
                }
                
                // 4. 获取响应流
                try (InputStream is = response.getEntity().getContent();
                     BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
                    String line;
                    // 5. 逐行读取并转发
                    while ((line = reader.readLine()) != null) {
                        // 6. 发送给前端
                        emitter.send(line);
                    }
                    
                    // 7. 完成后关闭SSE
                    emitter.complete();
                }
            } catch (Exception e) {
                // 8. 错误处理
                emitter.completeWithError(e);
            }
        });
        return emitter;
    }
}

使用 springboot WebFlux

新建高并发/代理外部流式 API → Flux + WebFlux(性能与扩展性更优)

响应式编程(Reactive Streams)

每个请求占用一个线程,高并发时资源消耗大

全链路非阻塞,适合代理外部流式 API

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId> <!-- 非阻塞IO -->
</dependency>

下面例子是转发大模型的响应结果

@RestController
public class WebFluxController {
    @GetMapping(value = "/flux-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> stream() {
        // 直接转发大模型的流式响应(非阻塞)
        WebClient webClient = WebClient.create("https://api.open-model.com/stream");
        return webClient.get()
            .retrieve()
            .bodyToFlux(String.class)
            .map(data -> "data: " + data + "\n\n") // 封装为 SSE 格式
            .delayElements(Duration.ofMillis(100)); // 非阻塞延迟
    }
}

使用 servlet

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
    // 设置响应头,声明内容类型为 SSE
    response.setContentType("text/event-stream");
    response.setCharacterEncoding("UTF-8");
    response.setHeader("Cache-Control", "no-cache");
    response.setHeader("Connection", "keep-alive");

    OutputStream outputStream = response.getOutputStream();

    try {
        // 模拟流式传输数据
        for (int i = 0; i < 5; i++) {
            String data = "event: message\ndata: {" + i + "}\n\n";
            outputStream.write(data.getBytes()); // 写入
            outputStream.flush(); // 不断的发送 flush
            Thread.sleep(1000); // 模拟延迟
        }

        // 发送自定义结束事件
        String endEvent = "data: [DONE]\n\n";
        outputStream.write(endEvent.getBytes());
        outputStream.flush();

        // 关闭流,通知前端结束
        outputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
        outputStream.close();
    }
}

如果是你的服务接入了大模型 api,大模型本身做的就是一个流式输出呢?承接后直接输出到前端

@WebServlet("/proxy-stream")
public class StreamingProxyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 设置响应头
        response.setContentType("text/event-stream");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Connection", "keep-alive");

        // 构造 JSON 请求体
        String jsonBody = "{\"param1\": \"value1\", \"param2\": \"value2\"}";

        // 创建 HttpClient 实例
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost("https://third-party.com/stream"); // 替换为你的流式接口地址
            // 设置请求头
            httpPost.setHeader("Content-Type", "application/json; charset=UTF-8");
            // 设置请求体
            httpPost.setEntity(new StringEntity(jsonBody, StandardCharsets.UTF_8));
            // 执行请求
            try (CloseableHttpResponse backendResponse = httpClient.execute(httpPost)) {
                // 检查后端响应状态码
                int statusCode = backendResponse.getStatusLine().getStatusCode();
                if (statusCode != 200) {
                    response.sendError(HttpServletResponse.SC_BAD_GATEWAY, "后端接口返回状态码: " + statusCode);
                    return;
                }

                // 获取后端流式接口的输入流
                InputStream backendStream = backendResponse.getEntity().getContent();
                // 获取前端响应的输出流
                OutputStream frontendStream = response.getOutputStream();
                byte[] buffer = new byte[4096];
                int bytesRead;

                // 边读边写,实时透传
                while ((bytesRead = backendStream.read(buffer)) != -1) {
                    frontendStream.write(buffer, 0, bytesRead);
                    frontendStream.flush(); // 必须立即刷新,确保数据实时到达前端
                }

                // 结束流式连接
                frontendStream.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "流式传输失败: " + e.getMessage());
        }
    }
}

扩展

自定义事件类型:

后端发送:event: update\ndata: {…}\n\n

前端监听:.addEventListener(“update”, handler)

总结

到此这篇关于java后端http接口流式输出到前端的文章就介绍到这了,更多相关java后端http接口流式输出到前端内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java简单几步实现一个二叉搜索树

    Java简单几步实现一个二叉搜索树

    二叉树包含了根节点,孩子节点,叶节点,每一个二叉树只有一个根节点,每一个结点最多只有两个节点,左子树的键值小于根的键值,右子树的键值大于根的键值,下面这篇文章主要给大家介绍了关于如何在Java中实现二叉搜索树的相关资料,需要的朋友可以参考下
    2023-02-02
  • 详解spring boot容器加载完后执行特定操作

    详解spring boot容器加载完后执行特定操作

    这篇文章主要介绍了详解spring boot容器加载完后执行特定操作,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • Java获取本机IP的几种常见方法

    Java获取本机IP的几种常见方法

    在Java编程中,我们经常需要获取本地或远程计算机的IP地址,IP地址是用于唯一标识计算机的一组数字,它在网络通信中起到重要的作用,下面将介绍几种在Java中获取IP地址的方法,并提供相应的源代码,需要的朋友可以参考下
    2025-05-05
  • JAVA transient 关键字作用详解

    JAVA transient 关键字作用详解

    Java的transient关键字用于修饰成员变量,使其不参与序列化过程,通过自定义序列化方法,可以手动控制transient变量的序列化行为,本文给大家介绍JAVA transient 关键字作用,感兴趣的朋友跟随小编一起看看吧
    2025-11-11
  • Java请求调用参数格式为form-data类型的接口代码示例

    Java请求调用参数格式为form-data类型的接口代码示例

    这篇文章主要给大家介绍了关于Java请求调用参数格式为form-data类型的接口的相关资料,文中给出了详细的代码示例,对大家的学习或者工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-08-08
  • 深入学习java8 中的CompletableFuture

    深入学习java8 中的CompletableFuture

    本文主要介绍了java8中的CompletableFuture,CompletableFuture实现了CompletionStage接口和Future接口,前者是对后者的一个扩展,增加了异步回调、流式处理、多个Future组合处理的能力,使Java在处理多任务的协同工作时更加顺畅便利,下文需要的朋友可以参考一下
    2022-05-05
  • 解析和解决org.springframework.beans.factory.NoSuchBeanDefinitionException异常问题

    解析和解决org.springframework.beans.factory.NoSuchBeanDefinitionE

    这篇文章主要介绍了解析和解决org.springframework.beans.factory.NoSuchBeanDefinitionException异常问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • java中java.util.Date和java.sql.Date之间的转换的示例

    java中java.util.Date和java.sql.Date之间的转换的示例

    java.util.Date是java.sql.Date的父类,有时候在和SqlServer数据库打交道时,也会遇到,本文主要介绍了java中java.util.Date和java.sql.Date之间的转换的示例,具有一定的参考价值,感兴趣的可以了解一下
    2024-05-05
  • Java中的排序与内部比较器Compareable解析

    Java中的排序与内部比较器Compareable解析

    这篇文章主要介绍了Java中的排序与内部比较器Compareable解析,一般没有特殊要求时,直接调用(底层默认的升序排列)就可以得到想要的结果,所谓的 sort 方法排序底层都是基于这两种排序,故如果需要设计成所想要的排序就需要了解底层排序原理,需要的朋友可以参考下
    2023-11-11
  • IDEA编译报错:Error:(2048,1024) java: 找不到符号的解决方案

    IDEA编译报错:Error:(2048,1024) java: 找不到符号的解决方案

    在使用 Lombok 的过程中,你是否曾遇到过 IDEA 编译报错 Error:(2048,1024) java: 找不到符号?下面就让我们来深入剖析这一问题的根源,并给出相应的解决方案,需要的朋友可以参考下
    2025-02-02

最新评论