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()
})

更多推荐