【JavaScript】快速入门 SSE 及其 POST 魔改
·
1. SSE 是以什么格式的数据发送的?
SSE(Server-Sent Events) 是一种 单向通信 的方式,仅仅允许服务器主动向客户端推送数据。
使用 EventSource 对象在客户端与服务器建立持久连接(通常是 HTTP 长连接),然后通过这个连接接收服务器推送的事件数据。
SSE(Server-Sent Events)的数据格式是 text/event-stream,即基于 UTF-8 编码的文本流,只支持 get 请求。
它有固定的格式规则:
-
每个事件由若干行组成,以
\n\n作为分隔符。 -
常用字段:
data:消息内容(可以多行,客户端会拼接成一个字符串),末行以双 \n\n 结束event:自定义事件名(默认是message),对应前端 addEventListener 的事件名id:消息 ID(客户端断线重连时可以用Last-Event-ID继续)retry:建议客户端重连间隔(单位:毫秒)
示例:
id: 1
event: message
data: 这是第一条消息
event: news
data: {"title":"新闻","content":"今天有新内容"}
// 前端
const sse = new EventSource('/api/stream')
sse.onopen = () => console.log('连接已建立')
sse.onmessage = (event) => {
const data = JSON.parse(event.data)
updateUI(data)
}
sse.addEventListener('custom', (event) => {
// 处理自定义事件类型
})
sse.onerror = (err) => console.error('连接出错')
2. 如何支持其他格式或者请求方式?
文本传输格式
SSE 传输的本质是 文本,所以:
- 如果要传 JSON → 用
JSON.stringify转成字符串再放到data: - 如果要传二进制(比如图片、文件) → 转成 Base64 或 Blob 的字符串 再传
- 如果要传自定义格式 → 依然先序列化成文本
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
data: {"message": "Hello, world!"}\n\n
import express from "express";
import cors from "cors";
const app = express();
app.use(cors());
app.use(express.json());
// 后端只需要修改下请求方式
app.post('/chat', (req, res) => {
res.setHeader("Content-Type", "text/event-stream"); //返回SSE
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
setInterval(() => {
res.write(`data: ${new Date().toISOString()}\n\n`);
}, 1000);
});
app.listen(3000, () => {
console.log("Server is running on port 3000");
});
前端接收时再做反序列化:
evtSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
console.log("收到 JSON:", data);
} catch {
console.log("收到文本:", event.data);
}
};
SSE 不支持真正的
body传输,它就是基于 HTTP 响应流。要支持其他格式,必须 自己封装成文本再传。
前端 fetch 魔改
传统方式:
// 前端
const sse = new EventSource('/api/stream')
sse.onopen = () => console.log('连接已建立')
sse.onmessage = (event) => {
const data = JSON.parse(event.data)
updateUI(data)
}
sse.addEventListener('custom', (event) => {
// 处理自定义事件类型
})
sse.onerror = (err) => console.error('连接出错')
- ⛔ 只能用 GET 方法,不能设置 method、body
- ⛔ 不能自定义请求头,传不了 Authorization: Bearer xxx ,无法认证
- ⛔ 不能传递查询参数之外的请求数据(URL 长度有限)
一般工作中,由于 sse 只能使用 get 请求,但是我们要使用 post 请求,只能基于 fetch 进行魔改。
async function createSSE(url, body, token) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`, // ✅ 可以传了!
},
body: JSON.stringify(body), // ✅ 可以传参数了!
})
if (!response.ok) throw new Error(`SSE 连接失败: ${response.status}`)
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 })
// 按 \n\n 分割,逐条解析 SSE 消息
const lines = buffer.split('\n\n')
buffer = lines.pop() // 最后一个可能不完整
for (const chunk of lines) {
const match = chunk.match(/^data: (.+)$/m)
if (match) {
const data = JSON.parse(match[1])
onMessage(data)
}
}
}
}
3. 应用场景
现在最主流的AI应用场景就是AI对话。
AI 聊天 / LLM 流式输出:前端向大模型 API 发送 prompt,服务端逐字返回 token。
// 前端 — 大模型对话
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'gpt-4',
messages: [{ role: 'user', content: prompt }],
stream: true,
}),
})
const reader = response.body.getReader()
// 逐步读取 token 并实时渲染到 UI
app.post('/api/chat', async (req, res) => {
// 1. 鉴权
const token = req.headers.authorization
if (!verifyToken(token)) return res.status(401).end()
// 2. 配置 SSE
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
})
// 3. 读取 body 参数
const { prompt } = req.body
// 4. 模拟流式输出
for (const word of ['你好', ',', '我是', 'AI']) {
res.write(`data: ${JSON.stringify({ token: word })}\n\n`)
await sleep(100)
}
res.write(`data: [DONE]\n\n`)
res.end()
})
更多推荐



所有评论(0)