首页
/ 用 Supabase Edge Functions 实现流式响应:基于 streams 示例的 SSE 实战指南

用 Supabase Edge Functions 实现流式响应:基于 streams 示例的 SSE 实战指南

2026-09-06 18:29:15作者:宣聪麟

本篇指南聚焦 Supabase 官方示例仓库中 streams 边缘函数(Edge Function),讲解如何在 Supabase Edge Functions 中通过 Web 标准 ReadableStream 向客户端持续推送 text/event-stream(SSE)数据。读完本文,你将掌握从本地运行、cURL 验证到云端部署一条完整的流式接口开发路径,并能基于仓库源码理解 withSupabase 鉴权包装、计时器清理等底层细节,从而把同样的模式迁移到实时通知、日志推送、AI 逐字输出等场景。

示例在仓库中的位置与定位

streamsexamples/edge-functions 目录下众多 Supabase Edge Function 示例之一。它的完整代码只由两个文件组成:

文件 作用
index.ts 函数唯一入口,实现流式 SSE 响应
README.md 说明本地运行与部署两条命令

该目录整体布局遵循 Supabase CLI 约定:所有函数都放在 supabase/functions/<函数名>/index.ts 下,函数级配置(如是否校验 JWT)集中在 supabase/config.toml 中。可以看到 config.toml 里专门有一段:

[functions.streams]
verify_jwt = true

这表示该函数在云端默认被配置为需要校验 JWT,这也与 index.tswithSupabase({ auth: 'user' }) 的鉴权设定保持一致。示例仓库还附带一个 app/ 测试客户端,可用来对本地及已部署函数发起测试请求。

源码逐行拆解:如何构建一个 SSE 流

先完整读一遍核心实现 index.ts

import { withSupabase } from 'npm:@supabase/server@^1'

const msg = new TextEncoder().encode('data: hello\r\n\r\n')

// Authenticated endpoint, so deploy with verify_jwt = true.
export default {
  fetch: withSupabase({ auth: 'user' }, (req, ctx) => {
    let timerId: number | undefined

    const body = new ReadableStream({
      start(controller) {
        timerId = setInterval(() => {
          controller.enqueue(msg)
        }, 1000)
      },
      cancel() {
        if (typeof timerId === 'number') {
          clearInterval(timerId)
        }
      },
    })

    return new Response(body, {
      headers: {
        'Content-Type': 'text/event-stream',
      },
    })
  }),
}

尽管只有三十来行,这段代码完整覆盖了流式边缘函数的全部关键要素。

1. withSupabase:统一的鉴权与 CORS 包装

第一行通过 npm: 前缀从 Deno 生态引入 @supabase/server 包(@^1 表示安装 1.x 最新版本)。withSupabase 是这个包的便捷包装器,它接收一个鉴权配置对象与一个处理函数:

  • { auth: 'user' } 表示要求调用者携带已登录用户的 JWT,函数内部才能拿到当前用户上下文;
  • 示例中注释也明确指出这是 "Authenticated endpoint",因此仓库 config.toml[functions.streams]verify_jwt = true 与之呼应。

横向对比同仓库其他示例可以发现该 API 的三种常见用法:

browser-with-cors 等示例中还有注释说明 "withSupabase handles CORS automatically",即该包装器同时负责处理跨域预检请求,省去手写 CORS 逻辑的工作。

2. ReadableStream:流的源头

函数返回值的核心是标准 Web API ReadableStream。它通过 start(controller) 回调在流建立后立即执行:

timerId = setInterval(() => {
  controller.enqueue(msg)
}, 1000)

也就是每 1000 毫秒向流中推入一条消息 msg,形成“每秒一次”的持续输出节奏。controller.enqueue() 是把数据块(chunk)交给下游消费者的标准方式。

3. cancel():及时清理定时器

ReadableStream 支持 cancel() 钩子:当客户端主动断开连接、或上层中止读取时,该回调会被触发。示例在这里调用 clearInterval(timerId) 停止定时器。这是一个很容易被遗漏但非常重要的生产级细节——如果客户端断开后定时器仍在运行,会造成无效的持续推送,浪费边缘运行时的执行时长与资源。仓库示例通过 typeof timerId === 'number' 先判断再清理,避免重复清理。

4. TextEncoder 与 SSE 消息格式

const msg = new TextEncoder().encode('data: hello\r\n\r\n')

TextEncoder 将字符串编码为 Uint8Array,因为流的 chunk 需要是二进制或字符串数据。编码后的内容遵循 SSE(Server-Sent Events)协议

  • data: hello:以 data: 开头定义一条消息的负载;
  • \r\n\r\n:空行分隔符,表示一条事件结束。SSE 规范要求事件之间用空行隔开,客户端(如浏览器 EventSource)正是依据这个空行把流切分成一条条独立事件。

因此任何兼容 SSE 的客户端收到的将是每隔一秒一个的 hello 事件。

5. ResponseContent-Type: text/event-stream

最后把 ReadableStream 作为响应体返回,并显式声明:

headers: {
  'Content-Type': 'text/event-stream',
}

该 MIME 类型告诉浏览器与代理:这是一个持续打开的流式响应,不应被当作普通 JSON 一次性读完。这也是 fetch 处理 SSE 时必须自行识别的关键头。示例中 fetch 方法签名带有 (req, ctx),其中 ctx 供需要访问请求上下文(如 Supabase 用户信息)的场景使用。

本地运行:从命令到请求链路

streams 示例的 README.md 给出了本地运行的最小步骤。

首先启动本地函数服务:

supabase functions serve --no-verify-jwt

serve 是 Supabase CLI 提供的本地开发命令,会以 watch 模式监听 supabase/functions 目录下的函数并在文件变更时自动重载。示例函数默认由 CLI 挂载到本机网关地址,按仓库惯例本地 API 端口为 54321(见 config.toml[api] port = 54321),函数通过固定前缀 functions/v1/ 暴露。

关于 --no-verify-jwt:该参数让本地网关跳过 JWT 校验,从而允许不带 Authorization 头的裸请求直接命中函数,便于用浏览器或 cURL 快速调试。这也与同仓库根级 examples/edge-functions/README.mdsupabase functions serve --env-file ./supabase/.env.local --no-verify-jwt 的开发约定一致。需要说明的是,serve 依赖本地 Supabase 栈(通常先用 supabase start 拉起 Docker 中的 Postgres/网关等容器),示例级目录本身并不包含 .env.local.example,如函数需要自定义环境变量,可参照根级 README 在项目内自行准备 .env 文件并通过 --env-file 指定。

按 README 建议,用 cURL 或 Postman 发起一个 GET 请求即可看到效果:

curl http://localhost:54321/functions/v1/streams

命令发出后,终端会像日志一样每秒钟打印一行 data: hello,直到你主动中断(Ctrl+C)。如果希望观察响应头,可以追加 -i

curl -i http://localhost:54321/functions/v1/streams

返回中会出现 Content-Type: text/event-stream,证明这是一个被正确声明的 SSE 流式连接,而不是一次性返回完的普通响应。

在浏览器端消费流:前端接入方式

由于该端点默认按 SSE 格式输出,前端可用原生 EventSource 在几行内接入(仅示例性示意,需根据业务自行封装):

const source = new EventSource('/functions/v1/streams')
source.onmessage = (event) => {
  console.log(event.data) // 每秒打印一次 "hello"
}

需要留意两个前提:

  1. 仓库 config.toml 与源码注释均表明该函数是需要用户鉴权的端点(verify_jwt = trueauth: 'user')。EventSource 无法自定义请求头,因此带 JWT 的接入需要改用 fetch + ReadableStream 手动解析 SSE,或先解决鉴权携带问题。
  2. 若仅做本地联调,serve --no-verify-jwt 下匿名即可访问;正式部署后行为取决于部署时写入的 verify_jwt 配置。

部署到云端

示例 README 的部署命令为:

supabase functions deploy --no-verify-jwt streams

其中 streams 是函数名(对应 supabase/functions/streams/ 目录)。这里需要特别澄清一个容易误解的点:虽然 README 用 --no-verify-jwt 便于裸 curl 直接访问,但仓库源码注释与 config.toml 都将其标记为认证端点(verify_jwt = true)。实际部署时应以你的业务安全需求为准——若希望云端强制鉴权,可去掉 --no-verify-jwt 让 config.toml 中的 verify_jwt = true 生效,此时调用方必须携带有效的用户 JWT。

完整部署前通常还需要完成以下前置步骤(详见 examples/edge-functions/README.md):

# 1. 登录并关联云端项目
supabase login
supabase link --project-ref your-project-ref

# 2. 若函数需要密钥等环境变量,先写入云端
supabase secrets set --env-file ./path/to/your.env

# 3. 确认密钥已生效
supabase secrets list

# 4. 部署指定函数
supabase functions deploy streams

仓库根级 README 还补充了两种工程化部署方式:其一,将函数级配置固化到 config.toml,例如把 verify_jwt 与 import map 等放在 [functions.<name>] 段下随项目版本管理;其二,通过 GitHub Actions 集成 supabase/setup-cli,在推送或合并到主干时自动执行 supabase functions deploy,实现 CI 化发布。

从示例出发:把流式能力用到真实场景

streams 示例演示了边缘函数流式响应的最小闭环,这套骨架可以直接扩展为多种生产场景:

  • 定时状态推送:把 setInterval 换成消息队列或数据库变化轮询,向订阅者持续广播状态;
  • AI 逐字输出:将大模型流式 token 通过 controller.enqueue() 按 SSE 格式逐块下推,用户侧实时渲染,这也是当前 AI 应用最常见的用法;
  • 长任务进度通知:在 start() 中开启任务、用 cancel() 保证客户端断开即释放资源。

无论哪种场景,有两个结构性要点值得沿袭自本例:

  1. SSE 帧格式必须规范:每条消息以 data: <内容>\r\n\r\n 结束,内容可扩展为 JSON 字符串以携带结构化数据;
  2. 资源生命周期管理:在 cancel() 中清理定时器或中止任务,避免断开连接后仍持续消耗边缘函数配额。

小结

通过 streams 这个精炼示例可以确认,Supabase Edge Functions 完全建立在 Web 标准之上:ReadableStream 负责生产数据,Response 携带 text/event-stream 声明协议,withSupabase 统一处理鉴权与 CORS,而 --no-verify-jwt 与 config.toml 的 verify_jwt 配合则给出了从本地裸调、到云端强制鉴权的完整控制手段。参考源码:函数入口config.toml示例根 README。将其作为模板,即可在最短时间内为自己的项目搭建一条可运行、可部署、可安全收敛的流式数据通道。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388