发布于 2026-01-06 0 阅读
0

Supabase Edge 函数:引入后台任务、临时存储和 WebSocket

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',
      },
    }
  )
})

Enter fullscreen mode Exit fullscreen mode

如果您测试流媒体版本,尝试上传超过 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',
      },
    }
  )
})

Enter fullscreen mode Exit fullscreen mode

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)
})

Enter fullscreen mode Exit fullscreen mode

性能和稳定性

在过去的几个月里,我们对 Edge Functions 进行了多项性能、稳定性用户体验方面的改进。虽然这些改进最终用户通常看不到,但它们是我们今天宣布的新功能的基础。

接下来是什么?

我们制定了令人振奋的2025年发展路线图。其中一项主要任务是提供可定制的计算限制(内存、CPU和执行时长)。我们将很快发布相关更新信息。

敬请关注本周即将推出的新功能。您将看到所有这些功能如何像乐高积木一样完美契合,从而简化您的开发工作。

关于LW13的更多信息

第一天 - Supabase AI 助手 V2
第二天 - Supabase 函数:后台任务和 WebSocket

构建阶段

01 - OrioleDB 公开测试版

社区聚会

文章来源:https://dev.to/supabase/supabase-edge-functions-introducing-background-tasks-ephemeral-storage-and-websockets-pe