我们非常激动地要宣布三个大家期待已久的功能:后台运行的任务、临时文件缓存和WebSocket功能。
从今天起,你可以在任何项目里用这些功能。我们一起看看你能用它们造出的酷炫东西。
背景任务(背景任务更口语化的表达可以是‘幕后任务’)有时候后端逻辑不仅仅是为了响应请求,还需要做更多的事情。例如,你可能需要处理一批文件并将结果上传到 Supabase Storage。或者从数据库表中读取多个条目,并为每个条目生成嵌入向量。
引入后台任务后,使用边缘功能执行这些长时运行的任务变得超级简单。
我们引入了一种新的方法 EdgeRuntime.waitUntil
,它接受一个Promise作为参数。这确保了函数不会在Promise解决之前结束。
免费项目后台任务最多可以运行150秒(2分30秒)。如果您使用付费计划,该限制将提升至400秒(6分40秒)。我们计划在未来几个月内引入更灵活的时限。
你可以订阅即将关闭的通知,这是通过监听 beforeunload
事件实现的。更多详细信息请参阅如何使用后台任务的指南。
边缘函数现在可以访问临时存储空间。这对于后台任务来说非常有用,因为它允许您读写 /tmp
目录中的文件以存储中间数据。
查看如何访问ephemeral storage指南。
示例(例如):解压一个 zip 文件并将其内容上传到 Supabase Storage让我们来看一个实际使用后台任务(Background Tasks)和临时存储(Ephemeral Storage)的例子。
想象你在创建一个相册应用程序。你希望用户能上传包含照片的压缩文件。你将在边缘函数里解压这些文件并将它们上传到存储。
最直接的方式之一是使用流:
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 req.body.pipeThrough(new ZipReaderStream())) {
// 将文件写入 Supabase 存储桶
const { error } = await supabase.storage
.from(uploadId)
.upload(entry.filename, entry.readable, {})
console.log('已上传', entry.filename)
}
return new Response(
JSON.stringify({
uploadId,
}),
{
headers: {
'content-type': 'application/json',
},
}
)
})
切换到全屏模式,切换出全屏模式.
如果你试用流版本,当你尝试上传超过100MB的zip文件时会出现内存限制错误。这是因为流版本需要将zip存档中的每个文件都加载到内存中。
我们可以改为将 zip 文件写入临时文件。然后,使用后台任务来解压并上传到 Supabase 存储。这样,我们只需分段读取 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) => {
// the file entry is read
const blobWriter = new BlobWriter()
const blob = await entry.getData(blobWriter)
if (entry.directory) {
return
}
// upload the file to Supabase Storage
await supabase.storage.from(uploadId).upload(entry.filename, blob, {})
numFilesUploaded += 1
console.log('uploaded', entry.filename)
})
)
await zipReader.close()
}
// 当Function Worker即将终止时,可以添加一个`beforeunload`事件监听器来接收通知。
// 可以利用此来记录日志和保存状态。
globalThis.addEventListener('beforeunload', (ev) => {
console.log('function about to terminate: ', ev.detail.reason)
console.log('已上传的文件数量: ', 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)
// 在后台任务中处理zip文件
// 调用EdgeRuntime.waitUntil()可以确保函数工作者不会在承诺完成之前退出。
EdgeRuntime.waitUntil(processZipFile(uploadId, '/tmp/' + uploadId))
// 返回新的响应
return new Response(
JSON.stringify({
uploadId,
}),
{
headers: {
'content-type': 'application/json',
},
}
)
})
全屏显示 退出全屏
WebSocket(实时通信协议)边缘功能现在支持建立入站(服务器端)和出站(客户端)的 WebSocket 连接。这为许多新的应用场景提供了可能。
构建到 OpenAI 实时 API 的经过认证的中继OpenAI最近推出了一项实时API(Realtime API),该API使用了WebSockets。完全在客户端实现这项功能比较棘手,因为这需要你公开你的OpenAI密钥。OpenAI建议搭建一个服务器来处理请求。
有了我们对 WebSockets 的新支持,您可以在 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("请求并非尝试升级到WebSocket。", { status: 400 })
}
// WebSocket 浏览器客户端不支持发送自定义头。
// 我们必须使用URL查询参数来提供用户的JWT。
// 请注意查询参数可能会在某些日志系统中被记录。
const url = new URL(req.url)
const jwt = url.searchParams.get('jwt')
if (!jwt) {
console.error('未提供认证令牌')
return new Response('未提供认证令牌', { status: 403 })
}
const { error, data } = await supabase.auth.getUser(jwt)
if (error) {
console.error(error)
return new Response('提供的令牌无效', { status: 403 })
}
if (!data.user) {
console.error('用户未通过认证')
return new Response('用户未通过认证', { status: 403 })
}
const { socket, response } = Deno.upgradeWebSocket(req)
socket.onopen = () => {
// 向OpenAI发起一个出站WebSocket连接
const url = 'wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01'
// 这在边缘函数中不是问题,因为此代码运行于边缘函数中
const openaiWS = new WebSocket(url, [
'realtime',
`openai-insecure-api-key.${OPENAI_API_KEY}`,
'openai-beta.realtime-v1',
])
openaiWS.onopen = () => {
console.log('已成功连接到OpenAI服务器。')
socket.onmessage = (e) => {
console.log('socket消息:', e.data)
// 只有当OpenAI的WebSocket已打开时才发送消息
if (openaiWS.readyState === 1) {
openaiWS.send(e.data)
} else {
socket.send(
JSON.stringify({
type: 'error',
msg: 'OpenAI连接尚未准备就绪',
})
)
}
}
}
openaiWS.onmessage = (e) => {
console.log(e.data)
socket.send(e.data)
}
openaiWS.onerror = (e) => console.log('OpenAI错误: ', e.message)
openaiWS.onclose = (e) => console.log('OpenAI会话已结束')
}
socket.onerror = (e) => console.log('socket错误:', e.message)
socket.onclose = () => console.log('socket已关闭。')
return response // 101 (切换协议)
})
全屏 / 退出全屏
性能及稳定性在最近几个月里,我们为边缘函数做了许多 性能和稳定性方面的提升 以及 开发人员体验的改进。虽然这些改进通常不会直接影响最终用户,但它们是我们今天要宣布的新功能的基础。
下一步会是什么呢?我们计划在2025年推出一个令人兴奋的路线图。其中一个主要优先事项是提供用户可自定义的计算资源限制(内存、CPU以及执行时间)。我们很快会公布这方面的更新消息。
本周即将发布的新品,敬请期待。你会发现这些新产品就像乐高积木一样拼合在一起,让您的开发生活更加轻松。
更多关于LW13的信息第一天:Supabase AI 助手 V2
第二天:Supabase 函数:后台任务和 WebSocket
01 - OrioleDB 公开 Alpha 版 (OrioleDB 发布)
共同学习,写下你的评论
评论加载中...
作者其他优质文章