@fastify/sse

中文文档
0.6.1发布于 2026/10/09MITTypeScriptCommonJS构建来源已验证近 30 天下载 162.2万

用于快速化的服务器事件插件

中文使用文档
文档目录24 节
  1. 特征
  2. 安装
  3. 快速启动
  4. API 密码
  5. 插件注册
  6. 路由配置
  7. 回复( sse. send) (来源)
  8. 回复.se.stream ()
  9. 连接管理
  10. 信件重放
  11. 属性和方法
  12. 自定义响应状态代码
  13. 高级使用
  14. 接受信头谈判
  15. 回落到定期答复( sse: 'dual' (中文(简体) )
  16. 基于请求逻辑的动态流线( R) sse: 'manual' (中文(简体) )
  17. 处理错误
  18. 自定义序列化
  19. 测试
  20. 客户端用户端
  21. 类型脚本
  22. 实例
  23. 与 se- v2 的比较
  24. 许可证

@ fastify/ sse (帮助)

NPM Version CI neostandard javascript style

Fastify 的服务器- Sent Evolution 插件 。 提供一等SSE支持,提供干净的API集成,会话管理,和流出能力.

特征

  • 🚀 清洁 API: 路由级 SSE 支持 { sse: true }
  • 📡 流线: 对 Node.js 流和同步执行器的原生支持
  • 🔄 重新连接: 内置信件重放 Last-Event-ID
  • 💓 心跳动:可配置的保有机制
  • 🎯 生命周期: 与快取钩和出错处理完全整合
  • 📝 类型脚本: 包含完整类型定义
  • ⚡ 业绩: 高效后压处理

安装

npm i @fastify/sse

快速启动

const fastify = require('fastify')({ logger: true })

// Register the plugin
await fastify.register(require('@fastify/sse'))

// Create an SSE endpoint
fastify.get('/events', { sse: true }, async (request, reply) => {
  // Send a message
  await reply.sse.send({ data: 'Hello SSE!' })

  // Send with full options
  await reply.sse.send({
    id: '123',
    event: 'update',
    data: { message: 'Hello World' },
    retry: 1000
  })
})

await fastify.listen({ port: 3000 })

API 密码

插件注册

await fastify.register(require('@fastify/sse'), {
  // Optional: heartbeat interval in milliseconds (default: 30000)
  heartbeatInterval: 30000,

  // Optional: default serializer (default: JSON.stringify)
  serializer: (data) => JSON.stringify(data)
})

路由配置

路由选择通过设置 SSE 处理 sse 字段。 有三个 表格,每个表格描述路径服务内容:

// SSE-only route: handler always streams. Clients that explicitly refuse
// SSE (e.g. `Accept: application/json`) receive 406 Not Acceptable.
// Ambiguous Accept headers (*/*, text/*, missing) admit SSE per RFC 9110.
fastify.get('/events', { sse: 'only' }, handler)

// Dual-mode route: the same handler serves both SSE and non-SSE clients.
// Only an explicit `text/event-stream` token routes to SSE; everything
// else falls through to the handler with `reply.sse` undefined, so the
// handler is expected to produce a non-SSE response in that branch.
fastify.get('/data', { sse: 'dual' }, handler)

// Manual route: no Accept negotiation. `reply.sse` is always attached and
// the handler decides at runtime whether to stream (by calling reply.sse.*)
// or to return a normal response. Useful for streaming APIs that signal
// streaming via the request body (e.g. OpenAI-style `{ stream: true }`)
// rather than the Accept header.
fastify.post('/v1/chat', { sse: 'manual' }, handler)

// Back-compat: `sse: true` behaves like `'dual'` for routing. If the
// handler is actually SSE-only and trips a TypeError on `reply.sse`
// because of a wildcard Accept, the plugin rethrows with a message
// that names `sse: 'only'` as the fix.
fastify.get('/legacy', { sse: true }, handler)

// Object form (supports per-route options):
fastify.get('/events', {
  sse: {
    kind: 'only',                // 'only' | 'dual' | 'manual' — omit for back-compat
    heartbeat: false,            // Disable heartbeat for this route
    serializer: customSerializer // Custom serializer for this route
  }
}, handler)

回复( sse. send) (来源)

发送SSE消息. 接受各种源类型 :

单个信件

// Simple data
await reply.sse.send({ data: 'hello' })

// Full SSE message
await reply.sse.send({
  id: '123',
  event: 'update',
  data: { message: 'Hello' },
  retry: 1000
})

// Plain string
await reply.sse.send('plain text message')

流源

// Async generator
async function* generateEvents() {
  for (let i = 0; i < 10; i++) {
    yield { id: i, data: `Event ${i}` }
    await sleep(1000)
  }
}
await reply.sse.send(generateEvents())

// Node.js Readable stream
const stream = fs.createReadStream('data.jsonl')
await reply.sse.send(stream)

// Transform existing stream
const transformStream = new Transform({
  transform(chunk, encoding, callback) {
    callback(null, { data: chunk.toString() })
  }
})
someSource.pipe(transformStream)
await reply.sse.send(transformStream)

回复.se.stream ()

创建用于管道运营的变换流 :

// Use with pipeline for efficient streaming
const { pipeline } = require('stream/promises')
const fs = require('fs')

fastify.get('/file-stream', { sse: true }, async (request, reply) => {
  const fileStream = fs.createReadStream('data.jsonl')

  // Parse each line as JSON and convert to SSE format
  const parseTransform = new Transform({
    transform(chunk, encoding, callback) {
      const lines = chunk.toString().split('\n').filter(Boolean)
      for (const line of lines) {
        try {
          const data = JSON.parse(line)
          this.push({ id: data.id, data })
        } catch (err) {
          // Skip invalid JSON lines
        }
      }
      callback()
    }
  })

  // Stream file data through SSE
  await pipeline(
    fileStream,
    parseTransform,
    reply.sse.stream(),
    reply.raw,
    { end: false }
  )
})

连接管理

fastify.get('/live', { sse: true }, async (request, reply) => {
  // Keep connection alive (prevents automatic close)
  reply.sse.keepAlive()

  // Send initial message
  await reply.sse.send({ data: 'Connected' })

  // Check if keepAlive was called
  console.log('Keep alive status:', reply.sse.shouldKeepAlive) // true

  // Set up periodic updates
  const interval = setInterval(async () => {
    if (reply.sse.isConnected) {
      await reply.sse.send({ data: 'ping' })
    } else {
      clearInterval(interval)
    }
  }, 1000)

  // Clean up when connection closes
  reply.sse.onClose(() => {
    clearInterval(interval)
    console.log('Connection closed')
  })
})

信件重放

处理客户端重新连接 Last-Event-ID数字 :

const messageHistory = []

fastify.get('/events', { sse: true }, async (request, reply) => {
  // Handle replay on reconnection
  await reply.sse.replay(async (lastEventId) => {
    // Find messages after the last received ID
    const startIndex = messageHistory.findIndex(msg => msg.id === lastEventId)
    const messagesToReplay = startIndex !== -1
      ? messageHistory.slice(startIndex + 1)
      : messageHistory

    // Send missed messages
    for (const message of messagesToReplay) {
      await reply.sse.send(message)
    }
  })

  // Send new message
  const newMessage = { id: Date.now(), data: 'New event' }
  messageHistory.push(newMessage)
  await reply.sse.send(newMessage)
})

属性和方法

属性

  • reply.sse.lastEventId: 客户端最后收到的事件ID(字符串 QQ 无效)
  • reply.sse.isConnected连接状态 (布尔)
  • reply.sse.shouldKeepAlive: 处理器完成后, 连接是否应该保持活性( boolean)

方法

  • reply.sse.send(source): 从各种来源类型发送 SSE 信件
  • reply.sse.stream(): 创建输油管业务转换流
  • reply.sse.keepAlive(): 处理器返回后防止自动关闭连接
  • reply.sse.close(): 手动关闭连接
  • reply.sse.replay(callback): 使用 Last-Event ID 处理消息重放
  • reply.sse.onClose(callback): 连接关闭时的注册回调
  • reply.sse.sendHeaders(statusCode?): 手动发送信头(称为在首字母上自动发送)

自定义响应状态代码

SSE 响应默认为 200 OK。要使用不同的状态,请 reply.code() 在第一个 SSE 写入之前,或将状态直接传递给一个 手动 reply.sse.sendHeaders() 电话 :

fastify.get('/events', { sse: 'only' }, async (request, reply) => {
  reply.code(201)
  await reply.sse.send({ data: 'created' })
})

fastify.get('/events-explicit', { sse: 'only' }, async (request, reply) => {
  reply.sse.sendHeaders(201)
  await reply.sse.send({ data: 'created' })
})

明确 sendHeaders() 参数优先于 reply.code()。 。 。 . 一旦第一个 SSE 写入 响应头, 稍后 reply.code() 或者说 sendHeaders() 电话没有效果.

高级使用

接受信头谈判

插件解析 Accept 每页眉 RFC 9110 第12.5.1节 决定是否将请求作为SSE。 决定取决于 路线被宣布类型 :

路线类型接受信头行为
sse: 'only'text/event-stream证监会
sse: 'only'*/*, (中文(简体) ), text/*缺少证监会
sse: 'only'application/json 包括:.406 无法接受
sse: 'only'…, text/event-stream;q=0406 无法接受
sse: 'dual'text/event-streamSSE 分支
sse: 'dual'*/*, (中文(简体) ), text/*缺少非SSE分支
sse: 'dual'application/json非SSE分支
sse: 'manual'(任何/缺失)运行时处理器决定
sse: true(路线与 'dual'(中文(简体) )+ 关于滥用的解释性错误

简而言之:明确 text/event-stream 总是通往SSE; 模糊 通配符到SSE的路由只有在您已经声明了路由 SSE 时才会使用. 一直这样 curl, (中文(简体) ), fetch(),邮递员默认只与 SSE 合作 结束点,同时保留双模式的预期,即 通配符更偏好更丰富的非SSE代表.

回落到定期答复(sse: 'dual'(中文(简体) )

双模式路线从一个单一的SSE和非SSE客户端服务 处理者。 插件附加 reply.sse 只有客户明确 请求 text/event-stream; 否则不使用 reply.sse 并且预计将产生正常的反应。 联合国 存在 :

fastify.get('/data', { sse: 'dual' }, async (request, reply) => {
  const data = await getData()

  if (reply.sse) {
    // SSE client — stream the data
    await reply.sse.send({ data })
  } else {
    // Regular client — return JSON
    return { data }
  }
})

SSE 特定响应标题( :Content-Type: text/event-stream (a) 在第一次审议之前,不得犯 reply.sse.send() 编号 : reply.sse.stream() 呼叫,所以一个处理器 装饰 reply.sse 不过 然后返回掉入到 Fastify 正常值的纯值 串行路径,而不破坏响应.

基于请求逻辑的动态流线( R)sse: 'manual'(中文(简体) )

一些API根据请求决定是否流 机构 相当 比起 Accept 头曰. OpenAI 兼容和其他 LLM 端点,用于 示例,返回 a text/event-stream 请求提出时的反应 { "stream": true } 和单一的JSON反对——客户端 不发送 Accept: text/event-stream 头头左右.

sse: 'manual' 盖上这个:它完全跳过接受者谈判, 总是附加 reply.sse,并让操作者在运行时决定 是否要流出 。 一样 'dual',SSE信头 懒惰地在 首个 reply.sse.send() 编号 : reply.sse.stream() 呼叫,所以一个处理器 永不流出 快速化的正常序列.

fastify.post('/v1/chat/completions', { sse: 'manual' }, async (request, reply) => {
  const completion = await model.run(request.body)

  if (request.body.stream) {
    // Stream tokens as SSE — headers commit on the first send().
    await reply.sse.send(completion.tokens) // async iterable / Readable / messages
    return
  }

  // Non-streaming client — return a normal JSON response.
  return completion.toJSON()
})

当处理器解决或 掷出(呼叫) reply.sse.keepAlive() 保持开放 用于出带 发送,那么 reply.sse.close() 完成时). 因为只有心跳 一旦响应作为 SSE 执行, 即非流回落 反应不会被心跳所破坏.

处理错误

fastify.get('/stream', { sse: true }, async (request, reply) => {
  try {
    async function* riskyGenerator() {
      yield { data: 'before error' }
      throw new Error('Something went wrong')
    }

    await reply.sse.send(riskyGenerator())
  } catch (error) {
    // Handle errors gracefully
    await reply.sse.send({
      event: 'error',
      data: { message: 'Stream error occurred' }
    })
  }
})

自定义序列化

// Plugin-level serializer
await fastify.register(require('@fastify/sse'), {
  serializer: (data) => {
    // Custom serialization logic
    return typeof data === 'string' ? data : JSON.stringify(data)
  }
})

// Route-level serializer
fastify.get('/custom', {
  sse: {
    serializer: (data) => `CUSTOM:${JSON.stringify(data)}`
  }
}, async (request, reply) => {
  await reply.sse.send({ data: 'test' }) // Outputs: "CUSTOM:\"test\""
})

测试

测试SSE端点用标准快活注射简化:

const response = await fastify.inject({
  method: 'GET',
  url: '/events',
  headers: {
    accept: 'text/event-stream'
  }
})

assert.strictEqual(response.statusCode, 200)
assert.strictEqual(response.headers['content-type'], 'text/event-stream')
assert.ok(response.body.includes('data: "Hello SSE!"'))

客户端用户端

<!DOCTYPE html>
<html>
<head>
  <title>SSE Client</title>
</head>
<body>
  <div id="messages"></div>

  <script>
    const eventSource = new EventSource('/events')
    const messagesDiv = document.getElementById('messages')

    eventSource.onmessage = function(event) {
      const data = JSON.parse(event.data)
      messagesDiv.innerHTML += '<div>' + JSON.stringify(data) + '</div>'
    }

    eventSource.addEventListener('update', function(event) {
      console.log('Update event:', JSON.parse(event.data))
    })

    eventSource.onerror = function(event) {
      console.error('SSE error:', event)
    }
  </script>
</body>
</html>

类型脚本

全部类型脚本支持包括:

import fastify from 'fastify'
import fastifySSE, { SSEMessage } from '@fastify/sse'

const app = fastify()
await app.register(fastifySSE)

app.get('/events', { sse: true }, async (request, reply) => {
  const message: SSEMessage = {
    id: '123',
    event: 'test',
    data: { hello: 'world' }
  }

  await reply.sse.send(message)

  // TypeScript knows about SSE properties
  console.log(reply.sse.isConnected) // boolean
  console.log(reply.sse.lastEventId) // string | null
  console.log(reply.sse.shouldKeepAlive) // boolean
})

实例

见 实例 完整工作示例目录 :

  • 基本用途 - 简单的SSE终点
  • 动态流 - 说吧 sse: 'manual',基于请求正文的 OpenAI 风格的流或 JSON
  • 很快会有更多例子出现...

与 se- v2 的比较

特性钉入- sse- v2@ fastify/ sse (帮助)
基础SSE✅✅
同步显示器✅✅
流支持✅增强
会话管理❌✅
最后活动ID❌✅
连接保健❌✅
快速整合有限满分
测试支持❌✅

许可证

麻省理工学院

版本与发布记录

@fastify/sse 共发布 7 个版本,首次发布于 2025/08/26;过去 12 个月发布了 3 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.6.1latest2026/10/091 个月前
0.6.02026/07/283 个月前
0.5.02026/07/014 个月前
0.4.02025/09/161.2 年前
0.3.02025/09/111.2 年前
0.2.02025/08/301.2 年前

关于 @fastify/sse 的常见问题

@fastify/sse 是什么?

用于快速化的服务器事件插件

如何安装 @fastify/sse?

在项目目录运行 npm install @fastify/sse,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:fastify(^5.x)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@fastify/sse 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@fastify/sse 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@fastify/sse 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.20.2。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@fastify/sse 可以商用吗?

@fastify/sse 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@fastify/sse 的下载量有多少?

根据 npm 官方下载统计,@fastify/sse 在 2026/10/15 – 2026/11/13 这 30 天内被下载 1,622,389 次,日均约 54,080 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@fastify/sse 还在维护吗?

最新版本 0.6.1 发布于 2026/10/09(1 个月前);至今共发布 7 个版本,其中过去 12 个月发布了 3 个;首次发布于 2025/08/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。