SSE 学习
一、行业现状结论1. 主流公有大模型OpenAI / 智谱 GLM / 通义千问 / 文心一言 / Claude100% 统一采用text/event-streamSSE 标准协议请求头Content-Type: application/jsonPOST 传参响应头Content-Type: text/event-stream流式分片返回分片格式data: 单条完整JSON\n\n结尾data: [DONE]\n\n标记结束2.text/plain仅用于自研内部流式接口不会出现在标准大模型开放 API仅两种场景使用自己封装中转服务不遵循 SSE 标准用换行分割 JSON 片段老旧自研 AI 服务无标准化事件、id、retry 机制。核心选型差异维度text/event-streamSSE大模型标准text/plain 纯文本分行流自研协议标准W3C 标准化协议有规范字段data/event/id/retry无官方标准自定义格式前端原生支持浏览器EventSource开箱即用自动断线重连只能用fetch ReadableStream手动切割换行断点续传支持Last-Event-ID断连后恢复上次输出无断点标记断开只能从头重新请求消息隔离双换行\n\n分割消息内容内部换行不干扰分片仅单换行分割JSON 内带换行容易切割错乱额外能力自定义事件类型、心跳、重连间隔配置仅纯文本输出无扩展能力大模型生态全部厂商统一工具 / SDK 天然适配仅内部项目自用无通用 SDK二、Demo 1标准大模型 SSEtext/event-stream完整示例以智谱 GLM 流式接口为例分三层客户端调用大模型、Java 中转服务 SseEmitter 转发、前端 EventSource 接收1curl 请求大模型原生接口SSE 响应curl --location https://open.bigmodel.cn/api/paas/v4/chat/completions \ --header Authorization: Bearer 你的API_KEY \ --header Content-Type: application/json \ --data { model: glm-5.2, messages: [{role: user, content: 写一段Java SSE代码}], stream: true }接口返回流响应头Content-Type: text/event-streamdata: {id:chatcmpl-xxx,choices:[{delta:{content:好的下面给你演示},index:0}]} data: {id:chatcmpl-xxx,choices:[{delta:{content:SseEmitter实现代码},index:0}]} data: [DONE]2Java WebClient 消费大模型 SSE 流后端中转import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Flux; public FluxString getGlmStream(String prompt) { String reqJson { model: glm-5.2, messages: [{role:user,content:%s}], stream: true } .formatted(prompt); return WebClient.create(https://open.bigmodel.cn) .post() .uri(/api/paas/v4/chat/completions) .header(Authorization, Bearer API_KEY) .contentType(org.springframework.http.MediaType.APPLICATION_JSON) .bodyValue(reqJson) // 声明接收SSE流 .accept(org.springframework.http.MediaType.TEXT_EVENT_STREAM) .retrieve() .bodyToFlux(String.class); }3SpringMVC SseEmitter 转发给前端GetMapping(/ai/sse/chat) public SseEmitter chatStream(String prompt) { SseEmitter emitter new SseEmitter(60000L); FluxString glmFlux getGlmStream(prompt); glmFlux.subscribe( chunk - { // 直接将大模型data分片推给前端 emitter.send(SseEmitter.event().data(chunk)); }, emitter::completeWithError, emitter::complete ); return emitter; }4前端 EventSource 原生监听 SSEconst source new EventSource(/ai/sse/chat?prompt写Java代码); source.onmessage (e) { const raw e.data; if (raw [DONE]) { source.close(); return; } const res JSON.parse(raw); const text res.choices[0].delta.content; document.getElementById(ans).innerText text; }; source.onerror () console.log(连接断开浏览器自动重连);三、Demo 2手写SseEmitterSseEmitter只是 SpringMVC 对 SSE 协议、异步输出、连接生命周期的封装底层本质就是操作HttpServletResponse输出text/event-stream格式文本。 手动手写分两种同步手写简单 Demo缺点会占用 Tomcat 工作线程异步手写生产可用不阻塞线程复刻 SseEmitter 异步逻辑前置知识SSE 响应固定三件套响应头缺一不可// 1. 媒体类型固定SSE resp.setContentType(text/event-stream;charsetUTF-8); // 2. 禁用缓存 resp.setHeader(Cache-Control, no-cache); // 3. 长连接保持 resp.setHeader(Connection, keep-alive);分片格式data: 内容\n\n双换行代表一条消息结束。方案 1同步手写 SSE简易 Demo不推荐高并发缺点整个流式过程占用 Tomcat 同步线程长连接会耗尽线程池import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; import java.io.PrintWriter; RestController public class RawSseController { // 对接大模型流式接口同步手写SSE返回前端 GetMapping(/raw/sync/sse) public void syncSse(RequestParam String prompt, HttpServletResponse resp) throws IOException, InterruptedException { // 固定SSE响应头 resp.setContentType(text/event-stream;charsetUTF-8); resp.setHeader(Cache-Control, no-cache); resp.setHeader(Connection, keep-alive); resp.flushBuffer(); PrintWriter out resp.getWriter(); // 模拟调用智谱GLM获取流式分片替换成真实WebClient调用逻辑 String[] chunks { {\delta\:{\content\:\手写SSE第一块\}}, {\delta\:{\content\:\手写SSE第二块\}}, [DONE] }; for (String chunk : chunks) { // SSE标准格式 data: xxx\n\n out.write(data: chunk \n\n); out.flush(); // 强制刷出缓冲区前端立刻收到 Thread.sleep(500); } out.flush(); out.close(); } }前端不变依旧用 EventSourceconst es new EventSource(/raw/sync/sse?prompttest); es.onmessage e { console.log(收到分片, e.data); };方案 2异步手写 SSE生产级复刻 SseEmitter 异步能力解决同步方案阻塞 Tomcat 线程的核心问题开启异步上下文startAsync()业务逻辑丢入独立线程池执行监听连接断开事件释放资源避免内存泄漏GetMapping(/raw/async/sse) public void asyncSse(RequestParam String prompt, HttpServletResponse resp) throws IOException { // 1. SSE标准响应头 resp.setContentType(text/event-stream;charsetUTF-8); resp.setHeader(Cache-Control, no-cache); resp.setHeader(Connection, keep-alive); resp.flushBuffer(); // 2. 开启Servlet异步上下文释放Tomcat主线程 AsyncContext asyncCtx resp.startAsync(); asyncCtx.setTimeout(60000); // 60秒超时和SseEmitter默认行为对齐 PrintWriter out resp.getWriter(); // 3. 监听连接关闭/超时事件做资源清理 asyncCtx.addListener(new AsyncListener() { Override public void onComplete(AsyncEvent event) throws IOException { out.close(); } Override public void onTimeout(AsyncEvent event) throws IOException { out.write(data: {\done\:true}\n\n); out.flush(); out.close(); asyncCtx.complete(); } Override public void onError(AsyncEvent event) throws IOException { out.close(); } Override public void onStartAsync(AsyncEvent event) {} }); // 4. 独立线程执行大模型流式拉取不占用Tomcat线程池 new Thread(() - { try { // 这里替换 WebClient 调用智谱GLM流式接口 String[] chunks { {\delta\:{\content\:\异步手写SSE分片1\}}, {\delta\:{\content\:\异步手写SSE分片2\}}, [DONE] }; for (String c : chunks) { out.write(data: c \n\n); out.flush(); Thread.sleep(400); } } catch (Exception e) { e.printStackTrace(); } finally { // 流式全部输出完毕关闭异步上下文 asyncCtx.complete(); } }).start(); }核心优势和 SseEmitter 对齐Tomcat 工作线程立刻释放能支撑大量并发长连接自动处理超时、前端主动关闭页面、网络异常断开完全不依赖SseEmitter任何 API纯原生 Servlet 实现。四、Demo 3自研 text/plain 分行流式无 SSE 协议仅内部中转服务使用响应头Content-Type: text/plain每条 JSON 用单换行\n分隔无data:前缀。1后端 Spring 接口输出 text/plain 流GetMapping(value /ai/plain/stream, produces MediaType.TEXT_PLAIN_VALUE) public void plainStream(String prompt, HttpServletResponse resp) throws IOException { resp.setContentType(text/plain;charsetUTF-8); resp.setHeader(Transfer-Encoding, chunked); PrintWriter out resp.getWriter(); // 模拟大模型分片每行一条完整JSON out.println({\content\:\第一段代码\,\done\:false}); out.flush(); Thread.sleep(300); out.println({\content\:\第二段代码\,\done\:false}); out.flush(); Thread.sleep(300); out.println({\content\:\\,\done\:true}); out.flush(); out.close(); }2前端 fetch ReadableStream 手动解析无 EventSource 可用async function fetchPlainStream() { const res await fetch(/ai/plain/stream?prompt测试, { method: GET }); const reader res.body.getReader(); const decoder new TextDecoder(utf-8); let buffer ; while (true) { const { done, value } await reader.read(); if (done) break; buffer decoder.decode(value); // 按换行切割分片 const lines buffer.split(\n); buffer lines.pop(); for (const line of lines) { if (!line.trim()) continue; const json JSON.parse(line); document.getElementById(ans).innerText json.content; if (json.done) reader.cancel(); } } } fetchPlainStream();五、关键补充区分容易混淆的点请求头永远是 application/json两种流式方案客户端 POST 上传参数时Content-Type: application/json不变区别只在服务返回的响应头。为什么大厂不用 text/plain需要前端手写复杂流切割逻辑EventSource 原生能力全部作废没有断线自动重连、断点续传网络抖动体验极差无统一结束标记、事件类型多模型对接要重复写适配代码。什么时候才用 text/plain 仅小型内部工具、单机 Demo不想引入 SSE 规范、不需要稳定长连接的场景线上面向用户的 AI 问答一律使用text/event-stream。

相关新闻

Vue Element UI组件库实战:企业级中后台开发指南

Vue Element UI组件库实战:企业级中后台开发指南

1. 初识饿了么UI库:Vue生态中的桌面端利器 第一次接触Element UI(俗称饿了么UI库)是在2018年的一个后台管理系统项目中。当时团队正从jQuery转向Vue技术栈,面对需要快速搭建的几十个管理页面,这个基于Vue 2.0的组件库简…

2026/7/19 9:30:07阅读更多 →
Cognition AI:从代码补全到系统认知的范式跃迁

Cognition AI:从代码补全到系统认知的范式跃迁

1. 这不是一场工具替代战,而是一次认知范式的迁移“Does Cognition AI Matter When We Already Have Claude Code, Cursor, and Copilot?”——这个标题一出来,我就在团队晨会上被拉住问了三遍。不是因为问题有多难,而是因为它精准戳中了当前…

2026/7/19 9:30:07阅读更多 →
2026最新小红书下载图片去水印方法,安全无风险保存教程

2026最新小红书下载图片去水印方法,安全无风险保存教程

日常收藏小红书优质图文素材时,平台默认保存的图片都会自带账号水印与平台标识,影响素材整理、个人收藏使用。2026年小红书平台规则已完成多次迭代,很多老旧去水印方法逐渐失效,不少第三方工具还存在隐私泄露、广告弹窗、画质压缩…

2026/7/19 9:28:07阅读更多 →
Selene PageObject 模式实战:构建可维护的 Python Web 测试框架的完整指南

Selene PageObject 模式实战:构建可维护的 Python Web 测试框架的完整指南

Selene PageObject 模式实战:构建可维护的 Python Web 测试框架的完整指南 【免费下载链接】selene User-oriented Web UI browser tests in Python 项目地址: https://gitcode.com/gh_mirrors/sel/selene 想要构建可维护、可扩展的 Python Web 自动化测试框…

2026/7/19 15:21:06阅读更多 →
Python循环控制:while/continue/break原理与应用

Python循环控制:while/continue/break原理与应用

1. Python循环控制三剑客:while/continue/break深度解析刚接触Python循环时,我们往往只满足于让代码跑起来,却忽略了循环控制的艺术。while、continue和break这三个看似简单的关键字,实则是构建高效循环逻辑的基石。它们就像汽车驾…

2026/7/19 15:21:06阅读更多 →
flutter_percent_indicator完全指南:打造精美进度指示器的终极方案

flutter_percent_indicator完全指南:打造精美进度指示器的终极方案

flutter_percent_indicator完全指南:打造精美进度指示器的终极方案 【免费下载链接】flutter_percent_indicator Flutter percent indicator library 项目地址: https://gitcode.com/gh_mirrors/fl/flutter_percent_indicator 在Flutter应用开发中&#xff0…

2026/7/19 15:21:06阅读更多 →
Bloom架构深度剖析:Rust实现的高性能反向代理核心原理

Bloom架构深度剖析:Rust实现的高性能反向代理核心原理

Bloom架构深度剖析:Rust实现的高性能反向代理核心原理 【免费下载链接】bloom :cherry_blossom: HTTP REST API caching middleware, to be used between load balancers and REST API workers. 项目地址: https://gitcode.com/gh_mirrors/bloom/bloom Bloom…

2026/7/19 15:21:06阅读更多 →
瑞德克斯平台:聚焦细节,看看外汇行情信息呈现的关键标准

瑞德克斯平台:聚焦细节,看看外汇行情信息呈现的关键标准

外汇市场信息更新频繁,平台口碑的形成更依赖长期一致性:入口是否好找、说明是否前后一致、提示是否稳定出现。围绕瑞德克斯平台,下面从稳定体验与信息呈现等角度做一次正面观察。外汇相关信息更新频繁,平台将关键提示与解释呈现得…

2026/7/19 15:21:06阅读更多 →
Python基础语法速览:给C++程序员的快速入门

Python基础语法速览:给C++程序员的快速入门

上篇聊了Python在机器人开发中的定位。今天正式进入Python的学习。但这篇不是从零开始的Python教程。你是有C基础的人,很多编程概念已经懂了——变量、函数、循环、面向对象,这些不用重新学。你需要知道的是:Python和C有什么不同?…

2026/7/19 15:19:06阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/19 0:01:04阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/19 0:01:04阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/19 0:01:04阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/19 0:01:04阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/19 0:01:04阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/19 0:01:04阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/18 22:49:46阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/19 14:50:26阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/18 18:49:35阅读更多 →