R2 怎么让浏览器直接上传大文件?

用户上传 200 MB 视频,如果先穿过你的 Worker,大文件流量就白白经过了你的计算层。

用预签名 URL:服务端用 R2 的 S3 凭证签一个有时限的 URL,浏览器直接 PUT 到 R2。文件特别大、或要断点续传时,再用分片上传。

预签名 URL 本质上是一个带时限的授权,所以它有自己的限制,也有几个会让前端踩坑的地方。下面按这个顺序讲。关于 R2 本身适合存什么,见 Cloudflare R2 适合存什么数据?。

预签名 URL 有哪些限制?

官方文档的要点:

  • 支持 GET、HEAD、PUT、DELETE,不支持 POST 表单上传。
  • 有效期 1 秒到 7 天。
  • 域名是 <ACCOUNT_ID>.r2.cloudflarestorage.com,自定义域名不支持预签名。

怎么签发预签名 URL?

用 AWS SDK v3:

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const s3 = new S3Client({
  region: "auto",
  endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
  credentials: { accessKeyId, secretAccessKey },
});

const url = await getSignedUrl(
  s3,
  new PutObjectCommand({ Bucket: "my-bucket", Key: key, ContentType: "image/png" }),
  { expiresIn: 600 },
);

accessKeyId 与 secretAccessKey 是 R2 API 令牌,应当作 secret 管理,不要写进代码或 wrangler.jsonc。

预签名 URL 泄露了怎么办?

它是 bearer token:拿到 URL 的人在过期前都能执行被授权的操作。所以它就是密码,有效期要短。把 ContentType 纳入签名可以限制上传的类型,也可以限制 key。

浏览器跨域使用要注意什么?

必须给 bucket 配 CORS。另外,过期的预签名 URL 返回的 403 不带 CORS 头,前端 JavaScript 读不到具体错误,所以要在过期前刷新。

文件特别大时怎么办?

用分片:createMultipartUpload 得到 uploadId,每片 uploadPart,最后 complete。适合文件特别大,或要在 Worker 里断点续传的场景。

规则:最小片 5 MiB(最后一片除外),除最后一片外所有片大小必须相同,最多 10,000 片。未完成的分片上传默认 7 天后自动 abort,可以用生命周期策略修改。在那之前,未完成的分片会占存储,所以别忘了设清理策略。

我在本地试过前两条:5 MiB 加 6 MiB 加末片的组合,在 complete 时被拒绝(错误码 10048);非末片只有 1 KiB 时被拒绝(10011)。

预签名 URL 的支持方法、有效期、域名、bearer token 性质和 CORS 行为,以及分片的 10,000 片上限和 7 天自动清理,来自官方文档,我没有验证过。片大小必须一致、非末片不小于 5 MiB 这两条在本地 wrangler dev 上跑过。官方限额页对分片上传的单片上限自相矛盾(表格与脚注分别写 TiB 与 GiB 量级的数字),以官方为准。限额与行为会变化,请再核对官方页面。