R2
R2 是对象存储服务。Worker 通过 r2_buckets 绑定访问存储桶。celld 将对象存放在节点通过 --bucket 打开的集群存储桶中,因此 R2 的持久性取决于该对象存储。节点未配置集群存储桶时,所有 R2 方法都会失败。API 的详细说明请参阅 Workers API 参考。
示例#
R2 示例 演示如何在 R2 存储桶中读取、写入和删除对象。
wrangler.jsonc
{
"name": "r2",
"main": "index.js",
"compatibility_date": "2026-01-01",
"r2_buckets": [
{ "binding": "FILES", "bucket_name": "example-files" }
]
}index.js
export default {
async fetch(request, env) {
const key = new URL(request.url).pathname.slice(1);
if (!key) return new Response("Use /KEY.", { status: 400 });
if (request.method === "PUT") {
await env.FILES.put(key, request.body, {
httpMetadata: {
contentType: request.headers.get("content-type") ??
"application/octet-stream",
},
});
return new Response(null, { status: 204 });
}
if (request.method === "DELETE") {
await env.FILES.delete(key);
return new Response(null, { status: 204 });
}
if (request.method === "GET") {
const object = await env.FILES.get(key);
if (object === null) return new Response("Not found.", { status: 404 });
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("etag", object.httpEtag);
return new Response(object.body, { headers });
}
return new Response("Method not allowed.", { status: 405 });
},
};API#
get(key, { range, onlyIf })返回带有流式响应体的R2ObjectBody,或返回null。范围可通过offset、length、suffix或Range请求头指定。head(key)返回对象记录,不包含响应体。put(key, value, { httpMetadata, customMetadata, onlyIf })接受字符串、ArrayBuffer、类型化数组、Blob或ReadableStream。条件不满足时返回null。delete(keys)删除一个键或一组键。list({ prefix, delimiter, cursor, startAfter })返回一页对象。createMultipartUpload(key)返回一个句柄。uploadPart(number, value)上传一个分片,complete(parts)将分片组装成对象。
键与存储#
每个绑定都有自己的 bucket_name。celld 将键 <key> 存储为对象 r2/<bucket_name>/<key>。键的长度为 1 至 1024 字节。流式 put() 的请求体超过 8 MiB 后会转为分片上传。delete() 每次最多删除 1000 个键。celld 没有公开存储桶 URL、预签名 URL 或 S3 端点;要公开提供这些内容,需要在前面设置一个 Worker。
对象存储会对部分键使用百分号编码:包括含有非 ASCII 字符、控制字符,或 % \ { } ^ ` [ ] " < > ~ # | * ? 中任意字符的键。例如,přehled.html 会变成 r2/<bucket_name>/p%C5%99ehled.html。每个空路径段变成 %,因此 photos/ 会变成 r2/<bucket_name>/photos/%。a/b、/a/b、a//b 和 a/b/ 是四个不同的对象,这与 Cloudflare R2 一致。list() 返回解码后的键,但按编码后的形式排序,并将 startAfter 与编码后的形式比较。Cloudflare R2 则按 UTF-8 字节排序。例如,Cloudflare R2 在 startAfter: "aa" 之后会返回 a~,但 celld 将 a%7E 与 aa 比较,因此会跳过该键。使用 celld 列表返回的 cursor 不会出现此问题。
如果 list() 遇到由其他工具写入、百分号解码后不是 UTF-8 的键,例如 a%FF,调用会失败。
celld 0.5.1 及更早版本会删除键中的所有空路径段。旧版本以 photos/ 写入的对象仍保存在 photos 下,list() 也会将其返回为 photos。
分片上传#
分片上传存在于创建它的节点上。分片规则请参阅分片上传文档。如果一个分片先于前面的分片到达,它会在内存中等待;等待中的分片总量最多为 256 MiB,达到上限后 celld 会拒绝更多分片。等待中的分片可以替换,已经写入对象存储的分片则不能替换,celld 会拒绝这种写入。
与 Cloudflare 的差异#
- R2 绑定使用集群存储桶中的
r2/<bucket_name>/前缀。 - 五个内容相关的 HTTP 头以对象头的形式存储。
customMetadata、cacheExpiry、校验和与存储类别一起存为一个 JSON 值,用户元数据名称为celld-r2;Azure Blob Storage 不允许元数据名称包含连字符,因此使用celld_r2。 - 其他工具写入的对象也可以通过绑定读取。其用户元数据成为
customMetadata,对象头成为httpMetadata。要写入完整记录,请使用celld r2 put。 celld r2 get|head|put|delete|list替代wrangler r2 object。命令直接读取集群存储桶,不需要正在运行的节点。- 对象的
version等于内容 ETag,因此相同内容会得到相同版本。 - 不支持
ssecKey和jurisdiction。 - 条件写入不能使用大于 8 MiB 的流式请求体。
createMultipartUpload()不接受校验和。- 分片上传无法在其他节点或重启后继续。celld 也无法替换对象存储中已经存在的分片。
- 乱序分片最多占用 256 MiB 内存,完成上传时不能更改已存储分片的顺序。
Cloudflare 兼容性页面列出了运行时 API 和不支持的服务。