为了账号安全,请及时绑定邮箱和手机立即绑定

Supabase 边缘函数新功能详解:后台任务、临时存储和 WebSocket 支持

我们非常激动地要宣布三个大家期待已久的功能:后台运行的任务、临时文件缓存和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 发布)

社区活动

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消