摘要:
SSE(Server-Sent Events)技术因其基于HTTP、原生支持以及实现简单的特点,非常适合大模型流式输出数据的场景,许多大模型厂商因此提供支持SSE的API。GPTBots也推出了支持SSE的对话API,近期对Web页面进行技术升级,SSE使用标准Event Source API被改造为基于fetch-event-source实现,从而支持POST请求传参。本文将围绕相关技术知识展开,进行对比分析并解析其原理。
一、什么是SSE
SSE(Server-Sent Events)是一种基于HTTP协议的服务器推送技术,允许服务器向客户端发送实时更新。它是HTML5标准的一部分,旨在提供一种轻量级的方式,使浏览器能够接收来自服务器的事件流。SSE的核心是EventSource接口,它为客户端提供了与服务器建立单向连接的能力,从而实现消息的实时传递。
SSE的工作原理非常简单:客户端通过HTTP请求向服务器发起连接,服务器在保持连接的同时,持续地以文本流的形式发送事件数据。客户端接收到数据后,可以通过JavaScript代码处理这些事件。
SSE的主要特点包括:
单向通信:服务器可以主动向客户端推送数据,但客户端无法通过同一连接向服务器发送数据。
自动重连:当连接意外断开时,浏览器会自动尝试重新连接。
轻量级协议:基于HTTP协议,易于实现和调试。
支持事件类型:支持自定义事件类型,便于对不同类型的数据流进行处理。
SSE非常适合大模型流式返回数据的场景,因此很多模型服务厂商提供了SSE的接口,前端接入后可以实现打字机效果,实时输出和展示大模型的回复内容。
二、SSE和WebSocket技术对比
SSE和WebSocket都是实现实时通信的技术,但它们的设计理念和适用场景存在显著差异。
特性 |
SSE |
WebSocket |
通信方向 |
单向(服务器 -> 客户端) |
双向(客户端 <-> 服务器) |
协议 |
基于HTTP协议 |
独立的WebSocket协议 |
连接数 |
每个客户端需要单独的HTTP连接 |
单个WebSocket连接支持双向通信 |
浏览器支持 |
原生支持,兼容性较好 |
需要浏览器支持WebSocket API |
重连机制 |
内置自动重连 |
需要手动实现重连逻辑 |
数据格式 |
纯文本(UTF-8编码) |
支持文本和二进制数据 |
实现复杂度 |
简单,基于HTTP |
较复杂,需要独立的协议实现 |
适用场景 |
实时新闻、日志流、事件通知等 |
聊天应用、多人游戏、实时协作等复杂场景 |
选择SSE还是WebSocket,取决于具体的业务需求。如果只需要服务器向客户端推送数据,且对通信性能要求不高,SSE是一个简单高效的选择;而如果需要双向通信或对性能要求较高,WebSocket则更为适合。
三、标准的EventSource API
在浏览器中,SSE通过EventSource
对象实现。以下是EventSource
API的主要特性和用法:
1. 创建EventSource对象
使用EventSource
对象时,需要指定服务器的URL:
const eventSource = new EventSource('https://example.com/sse');
2. 事件监听
EventSource
支持三种事件类型:
message
:处理默认事件。open
:连接成功时触发。error
:连接出错或断开时触发。
示例代码:
eventSource.onopen = function(event) {
console.log('Connection opened:', event);
};
eventSource.onmessage = function(event) {
console.log('Message received:', event.data);
};
eventSource.onerror = function(event) {
console.error('Error occurred:', event);
};
3. 自定义事件类型
服务器可以发送自定义事件类型,客户端可以通过addEventListener
监听:
eventSource.addEventListener('custom-event', function(event) {
console.log('Custom event received:', event.data);
});
4. 关闭连接
可以通过调用close()
方法手动关闭连接:
eventSource.close();
console.log('Connection closed');
5. 服务器发送数据格式
服务器需要按照以下格式发送数据:
data: Hello, World!
event: custom-event
id: 12345
retry: 3000
data
:事件数据。event
:事件类型(可选)。id
:事件ID,用于断线重连时恢复状态(可选)。retry
:指定重连间隔时间(以毫秒为单位,可选)。
四、@microsoft/fetch-event-source 介绍
@microsoft/fetch-event-source
是微软开发的一个轻量级库,用于在Node.js和浏览器环境中实现SSE(Server-Sent Events)。与原生的EventSource
相比,它提供了更多的灵活性和功能,例如支持自定义HTTP方法(如POST)、请求头、认证、请求体等。
npm仓库:@microsoft/fetch-event-source - npm。
1. 安装
可以通过npm或yarn安装:
npm install @microsoft/fetch-event-source
2. 使用示例
以下是一个简单的使用示例:
import { fetchEventSource } from '@microsoft/fetch-event-source';
fetchEventSource('https://example.com/sse', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer YOUR_TOKEN',
},
body: JSON.stringify({ key: 'value' }),
onopen(response) {
if (response.ok && response.headers.get('content-type') === 'text/event-stream') {
console.log('Connection established');
} else {
console.error('Connection failed');
}
},
onmessage(event) {
console.log('Message received:', event.data);
},
onclose() {
console.log('Connection closed by server');
},
onerror(err) {
console.error('Error occurred:', err);
},
});
3. 特性
支持自定义HTTP方法(如POST)。
支持请求头和请求体。
提供事件钩子(如
onopen
、onmessage
、onclose
、onerror
)。支持断线重连。
五、@microsoft/fetch-event-source 原理解析
@microsoft/fetch-event-source
的实现基于fetch
API,通过流式处理实现了SSE的功能。以下是其核心实现原理的解析:
1. 核心逻辑
该库的核心逻辑是使用fetch
API发起HTTP请求,并通过ReadableStream
解析服务器返回的事件流。
源码片段(简化版):
async function fetchEventSource(url, options) {
const response = await fetch(url, options);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop(); // 保留未完整的行
for (const line of lines) {
if (line.startsWith('data:')) {
const data = line.slice(5).trim();
options.onmessage({ data });
}
}
}
options.onclose();
}
2. POST传参的实现
与原生EventSource
不同,该库允许通过POST方法发送参数。实现方式是将请求体传递给fetch
API:
fetch(url, {
method: 'POST',
headers: options.headers,
body: options.body,
});
3. 自动重连
当连接断开时,库会自动尝试重新连接:
setTimeout(() => {
fetchEventSource(url, options);
}, retryInterval);
4. 错误处理
通过try-catch
捕获错误,并调用onerror
回调:
try {
// 读取流数据
} catch (err) {
options.onerror(err);
}