Supabase Edge 函数:引入后台任务、临时存储和 WebSocket
我们很高兴地宣布推出三个期待已久的功能:后台任务、临时文件存储和 WebSocket。
从今天起,你可以在任何项目中使用这些功能。让我们一起来探索一下你可以用它们构建哪些精彩的内容。
背景任务
有时,后端逻辑需要执行的操作不仅仅是响应请求。例如,您可能需要处理一批文件并将结果上传到 Supabase 存储。或者,您可能需要从数据库表中读取多个条目,并为每个条目生成嵌入。
引入后台任务后,使用 Edge Functions 执行这些长时间运行的工作负载变得非常容易。
我们引入了一个名为 `resolve` 的新方法EdgeRuntime.waitUntil,它接受一个 Promise 对象。这确保了在 Promise 被解析之前,函数不会终止。
免费项目最多可以运行 150 秒(2 分 30 秒)的后台任务。如果您使用的是付费套餐,则此限制将增加到 400 秒(6 分 40 秒)。我们计划在未来几个月内推出更灵活的限制。
您可以通过监听事件来订阅函数即将关闭时的通知beforeunload。请阅读指南以了解有关如何使用后台任务的更多详细信息。
临时存储
Edge 函数调用现在可以访问临时存储。这对于后台任务非常有用,因为它允许您读取和写入/tmp目录中的文件来存储中间结果。
请查看如何访问临时存储的指南。
示例:解压缩 zip 文件并将其内容上传到 Supabase 存储
让我们来看一个使用后台任务和临时存储的实际例子。
假设你正在开发一个相册应用。你希望用户以压缩文件的形式上传照片。你会使用边缘函数解压缩这些文件,然后将它们上传到存储空间。
最直接的实现方式之一是使用流:
import { ZipReaderStream } from 'https://deno.land/x/zipjs/index.js'
import { createClient } from 'jsr:@supabase/supabase-js@2'
const supabase = createClient(
Deno.env.get('SUPABASE_URL'),
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')
)
Deno.serve(async (req) => {
const uploadId = crypto.randomUUID()
const { error } = await supabase.storage.createBucket(uploadId, {
public: false,
})
for await (const entry of await req.body.pipeThrough(new ZipReaderStream())) {
// write file to Supabase Storage
const { error } = await supabase.storage
.from(uploadId)
.upload(entry.filename, entry.readable, {})
console.log('uploaded', entry.filename)
}
return new Response(
JSON.stringify({
uploadId,
}),
{
headers: {
'content-type': 'application/json',
},
}
)
})
如果您测试流媒体版本,尝试上传超过 100MB 的 zip 文件时,会遇到内存限制错误。这是因为流媒体版本需要将 zip 压缩包中的每个文件都保存在内存中。
我们可以修改代码,将 zip 文件写入临时文件。然后,使用后台任务将其解压缩并上传到 Supabase Storage。这样,我们只需将 zip 文件的部分内容读取到内存中。
import { BlobWriter, ZipReader, ZipReaderStream } from 'https://deno.land/x/zipjs/index.js'
import { createClient } from 'jsr:@supabase/supabase-js@2'
const supabase = createClient(
Deno.env.get('SUPABASE_URL'),
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')
)
let numFilesUploaded = 0
async function processZipFile(uploadId, filepath) {
const file = await Deno.open(filepath, { read: true })
const zipReader = new ZipReader(file.readable)
const entries = await zipReader.getEntries()
await supabase.storage.createBucket(uploadId, {
public: false,
})
await Promise.all(
entries.map(async (entry) => {
// read file entry
const blobWriter = new BlobWriter()
const blob = await entry.getData(blobWriter)
if (entry.directory) {
return
}
// write file to Supabase Storage
await supabase.storage.from(uploadId).upload(entry.filename, blob, {})
numFilesUploaded += 1
console.log('uploaded', entry.filename)
})
)
await zipReader.close()
}
// you can add a `beforeunload` event listener to be notified
// when Function Worker is about to terminate.
// use this to do any logging, save states.
globalThis.addEventListener('beforeunload', (ev) => {
console.log('function about to terminate: ', ev.detail.reason)
console.log('number of files uploaded: ', numFilesUploaded)
})
async function writeZipFile(filepath, stream) {
await Deno.writeFile(filepath, stream)
}
Deno.serve(async (req) => {
const uploadId = crypto.randomUUID()
await writeZipFile('/tmp/' + uploadId, req.body)
// process zip file in a background task
// calling EdgeRuntime.waitUntil() would ensure
// function worker wouldn't exit until the promise is completed.
EdgeRuntime.waitUntil(processZipFile(uploadId, '/tmp/' + uploadId))
return new Response(
JSON.stringify({
uploadId,
}),
{
headers: {
'content-type': 'application/json',
},
}
)
})
WebSocket
边缘函数现在支持建立入站(服务器)和出站(客户端)WebSocket连接。这为各种新的应用场景提供了可能。
示例:构建一个经过身份验证的 OpenAI Realtime API 中继
OpenAI 最近推出了一个使用 WebSocket 的实时 API。由于需要公开 OpenAI 密钥,因此纯客户端实现起来比较棘手。OpenAI建议构建一个服务器来验证请求。
借助我们对 WebSocket 的全新支持,您无需搭建任何基础设施即可在 Edge Functions 中轻松实现此功能。此外,您还可以使用Supabase Auth来验证用户身份,并保护您的 OpenAI 使用免受滥用。
import { createClient } from 'jsr:@supabase/supabase-js@2'
const supabase = createClient(
Deno.env.get('SUPABASE_URL'),
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')
)
const OPENAI_API_KEY = Deno.env.get('OPENAI_API_KEY')
Deno.serve(async (req) => {
const upgrade = req.headers.get('upgrade') || ''
if (upgrade.toLowerCase() != 'websocket') {
return new Response("request isn't trying to upgrade to websocket.")
}
// WebSocket browser clients does not support sending custom headers.
// We have to use the URL query params to provide user's JWT.
// Please be aware query params may be logged in some logging systems.
const url = new URL(req.url)
const jwt = url.searchParams.get('jwt')
if (!jwt) {
console.error('Auth token not provided')
return new Response('Auth token not provided', { status: 403 })
}
const { error, data } = await supabase.auth.getUser(jwt)
if (error) {
console.error(error)
return new Response('Invalid token provided', { status: 403 })
}
if (!data.user) {
console.error('user is not authenticated')
return new Response('User is not authenticated', { status: 403 })
}
const { socket, response } = Deno.upgradeWebSocket(req)
socket.onopen = () => {
// initiate an outbound WebSocket connection to OpenAI
const url = 'wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01'
// openai-insecure-api-key isn't a problem since this code runs in an Edge Function
const openaiWS = new WebSocket(url, [
'realtime',
`openai-insecure-api-key.${OPENAI_API_KEY}`,
'openai-beta.realtime-v1',
])
openaiWS.onopen = () => {
console.log('Connected to OpenAI server.')
socket.onmessage = (e) => {
console.log('socket message:', e.data)
// only send the message if openAI ws is open
if (openaiWS.readyState === 1) {
openaiWS.send(e.data)
} else {
socket.send(
JSON.stringify({
type: 'error',
msg: 'openAI connection not ready',
})
)
}
}
}
openaiWS.onmessage = (e) => {
console.log(e.data)
socket.send(e.data)
}
openaiWS.onerror = (e) => console.log('OpenAI error: ', e.message)
openaiWS.onclose = (e) => console.log('OpenAI session closed')
}
socket.onerror = (e) => console.log('socket errored:', e.message)
socket.onclose = () => console.log('socket closed')
return response // 101 (Switching Protocols)
})
性能和稳定性
在过去的几个月里,我们对 Edge Functions 进行了多项性能、稳定性和用户体验方面的改进。虽然这些改进最终用户通常看不到,但它们是我们今天宣布的新功能的基础。
接下来是什么?
我们制定了令人振奋的2025年发展路线图。其中一项主要任务是提供可定制的计算限制(内存、CPU和执行时长)。我们将很快发布相关更新信息。
敬请关注本周即将推出的新功能。您将看到所有这些功能如何像乐高积木一样完美契合,从而简化您的开发工作。
关于LW13的更多信息
第一天 - Supabase AI 助手 V2
第二天 - Supabase 函数:后台任务和 WebSocket