☰
SSE与Streamable HTTP的区别:从传输格式到API契约
2026/10/21 14:18:48 网站建设 项目流程

1. 为什么这两个概念总被搞混

最近在对接一个大模型Agent类接口,需求文档里赫然写着"要求支持Streamable HTTP"。我第一反应是:这不就是SSE吗?网上教程里全是text/event-stream的解析代码,很多文章甚至直接把Streamable HTTP和SSE当成同义词来用。直到真正动手做技术方案评审,被问到"Streamable HTTP到底解决的是传输问题还是协议设计问题"时,我才意识到自己其实根本没想清楚这两个概念的边界。

这种困惑不是个例。我见过不少开发者,包括一些写了多年后端的老手,在面对AI流式接口时都把"传输格式"和"接口契约"混在一谈。实际上,这两者处于完全不同的层级:SSE是一种基于HTTP的传输协议格式,而Streamable HTTP是一套API交互契约。打个比方:SSE就像高速公路上的货车标准,规定货物怎么码放、怎么运输;Streamable HTTP则是物流公司的服务章程,规定你下单后收到什么、中途怎么跟踪、异常怎么处理。公路标准服务于任何运输业务,服务章程则依赖某个具体的运输载体来落地。

这篇文章就把这两个概念彻底拆开揉碎:先讲清楚SSE的底层原理和格式细节,再拆解Streamable HTTP到底是什么、为什么AI大模型的Agent类接口要提这个要求,然后给出一张本质区别对比表和三句话判断标准,最后用一个可运行的最小集成方案落地演示,附上我踩过的坑和排查清单。无论你是第一次接触流式接口,还是已经在接各类大模型API但没深究过理论,这篇都适合你。

2. 先搞清楚SSE:这个"老兵"究竟解决了什么问题

2.1 SSE是传输格式,不是API设计

SSE全称Server-Sent Events,中文一般叫服务器推送事件。它是HTML5时代就定下来的标准,早在2011年前后就已经在规范里了。这个技术的目标非常朴素:让服务器在HTTP连接上主动向客户端推送数据,而且是持续地、多次地推送。

理解SSE的关键在于意识到它不是一种API设计理念,而是一种线缆上的数据格式约定。它规定了三件事:

  • 响应的MIME类型必须是text/event-stream
  • 数据要按"事件块"来组织,每个事件块用空行分隔
  • 事件块内部用若干字段行来描述,核心字段是data:、event:、id:、retry:

仅此而已。至于你的API是REST风格还是RPC风格,你的业务是聊天还是股票行情,SSE完全不关心。它只负责一件事:把服务器想推给客户端的数据,按标准格式编排好,顺着HTTP链接传过去。这就像一个通用的管道,谁来用都行。

2.2 SSE的格式细节与握手

一个最典型的SSE响应长这样:

HTTP/1.1 200 OK Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive data: {"role":"assistant","content":"你"} data: {"role":"assistant","content":"好"} data: {"role":"assistant","content":",很高兴认识你"} data: [DONE]

注意几个细节:

第一,响应头里除了Content-Type: text/event-stream,还需要Cache-Control: no-cache,防止中间缓存把流给吞了。如果是Nginx反向代理,通常还得加X-Accel-Buffering: no,否则Nginx默认会缓冲响应,等攒够一大块才交给客户端,流的实时性就没了。

第二,每条消息之间必须有一个空行。这个空行是分隔符,客户端解析时按空行切分事件块。很多新手第一次手写SSE时漏了空行,结果所有数据粘在一起,解析全乱。

第三,data:后面可以有多个值。实际上规范允许一个事件块里出现多行data:,客户端解析时会用换行符把它们拼成一条消息。例如:

data: {"type":"content_delta"} data: {"delta":"这是增量内容"}

这两个data:会被拼成一行{"type":"content_delta"}\n{"delta":"这是增量内容"}再触发事件回调。但我在实际开发中很少这么用,大多数情况下一个事件块就带一个JSON字符串,逻辑更清晰。

第四,event:字段用来给事件命名。不写event:时,默认事件名是message,浏览器端的EventSource会用onmessage接住。如果指定了event: tool_call,那客户端就要addEventListener('tool_call', handler)来接收。大模型的流式接口里,事件类型往往不止一种:有文本增量、有工具调用请求、有会话完成标记,甚至还有错误事件,所以命名事件是必须的。

第五,id:字段用于断线重连。浏览器原生的EventSource会记住最后一次收到的id,断线重连时通过请求头Last-Event-ID告诉服务器"我上次收到第几条了,你从下一条开始推"。这相当于一个天然的断点续传机制。

2.3 SSE与传统HTTP和WebSocket的差异

很多人在选型时会把SSE和WebSocket放在一起比,这里有个核心认知必须先建立:SSE走的是普通HTTP通道,不是独立协议。这意味着它天然具备HTTP生态的所有优势——穿透性好、可以复用负载均衡、可以通过CDN边缘节点、鉴权体系直接沿用HTTP的Cookie或Header机制。

而WebSocket是一条独立的TCP长连接,握手阶段虽然走HTTP Upgrade,但之后完全脱离HTTP语义。它需要专门的网关和连接管理,在私有化部署时往往要额外开端口、配负载均衡策略,运维成本明显高一个档次。

但它们解决的是不同问题。WebSocket是双向的,服务器能推、客户端也能随时发;SSE是单向的,只能服务器向客户端推。对于AI对话场景,用户的问题是普通HTTP请求提交的,模型的结果是流式返回的,这个"半双工"模型SSE恰好完美覆盖。WebSocket反而是杀鸡用牛刀,还得处理心跳、粘包、连接状态同步一堆破事。

顺便说一个关键限制:浏览器原生的EventSource只支持GET请求,而且无法自定义请求头。如果你的流式接口需要POST鉴权(比如Authorization: Bearer xxx放Header里),那原生EventSource根本用不了。这时候要么改用fetch+ReadableStream手动解析SSE格式,要么调整鉴权方式把token塞进查询参数。这个坑我在实战部分会详细展开。

3. Streamable HTTP:很多人其实没看懂它

3.1 它是一套API契约,不是传输协议

Streamable HTTP这个说法,最早是大模型服务商在Agent/Assistant类接口里提出来的兼容性要求。它要解决的核心矛盾是:传统的HTTP API是一问一答的模式,请求发出去,响应一次性回来。那响应体里有完整的JSON结构,客户端解析一下就行。但AI的流式交互不是这样——大模型生成一段文本可能需要几秒甚至几十秒,如果让用户干等到完整结果返回,体验极差。而且Agent类接口不仅仅是文本流,还包括中途调用工具、等待工具结果、再继续生成,一个请求的生命周期里可能发生很多事件。

于是服务商设计了一套新的接口行为规范,并起了个名字叫Streamable HTTP。它描述的是一整套交互契约,大约包含这些约定:

  • 客户端发起请求后,服务端可以立即返回一个复合响应:一部分是结构化数据(比如会话ID、资源状态),另一部分是实时的事件流
  • 事件流通过HTTP持久连接推送,事件类型有明确约定,比如文本增量、工具调用、运行完成、失败等
  • 客户端消费完流式响应后,可以再通过普通HTTP请求查询最终结果,或发起新的操作
  • 整个交互符合"请求-流响应-轮询/重放"的组合模式

说白了,Streamable HTTP是API语义层面的设计,它规定了"客户的请求应该得到什么形态的响应""流里的事件类型怎么命名""哪种情况下客户端应该重试"。至于流本身用什么格式传输,规范通常会推荐用SSE文本流,因为这些都是基于浏览器和移动端生态最兼容的现成方案。但SSE只是它的推荐载体,不是它的定义。

3.2 标准交互流程拆解

我以一个典型的Agent类流式会话为例,拆解Streamable HTTP下的完整请求生命周期。

第一步,客户端发起请求,通常是POST,请求体里包含用户输入和会话上下文。这个请求和普通API请求没有本质区别,一样走HTTPS。

第二步,服务端收到请求,创建了一个后台运行任务,然后立刻返回HTTP 200,响应头Content-Type: text/event-stream。注意,服务端必须在很短时间内返回这个响应头,而不是等模型跑完再响应。否则客户端会一直等待,体验和同步请求没区别。

第三步,流开始推送事件。第一个事件通常是会话状态快照,里面带有资源ID和当前状态。接着是若干message.delta增量事件,每推几

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询