对象存储云工适配
把公共[对象存储接口](../../../豫言操作系统接口/对象存储/规范.汉语.md)映射到当前 Worker 许可中的 R2 绑定(许可.json 例:{"R2":["PACKAGES"]},桶名由应用传入),全部用豫言写在 云工宿主的通用句柄桥之上(云工绑定句柄('R2', 桶名) 与 云工调用方法安全),不含宿主 JS。
| 接口函数 | R2 调用 |
| --- | --- |
| 取对象元数据 | head(键),读 size、etag、httpEtag、uploaded(Date 经 getTime)、httpMetadata、customMetadata |
| 读取对象文字 | get(键);size 超过上限则不读正文;否则 arrayBuffer() 后用 new TextDecoder('utf-8', {fatal: true, ignoreBOM: true}) 严格解码 |
| 读取对象字节 | get(键);size 超过上限则不读正文并抛异常;否则 arrayBuffer() 后用 云工句柄取字节 直接取字节串(不经 JSON) |
| 写入对象、写入对象文字 | put(键, Uint8Array, {onlyIf: Headers, sha256, httpMetadata, customMetadata});返回 null 即前置条件不满足 |
| 删除对象 | delete([键…]) |
| 列举对象 | list({limit, prefix, cursor, delimiter, include}) |
| 回应入站以对象存储对象 | GET:get(键) 后把 body 流交给 new Response(body, {status, headers}) 并 设置响应句柄;HEAD:head(键) 与无体的 Response |
要点:
- JSON 字面原样传出:对象的键、
etag、httpEtag、customMetadata、正文文字都是宿主JSON.stringify给出的合法字面,适配不在豫言里解码再重新编码(豫言的 JSON 编码器对控制字符的转义不可依赖,且大文字的逐字符转义太慢);只有整数(大小、毫秒)和httpMetadata的五个字符串字段会解析。因此读取对象文字的上限取 2 MiB:控制字符最坏膨胀六倍,仍低于值桥的 16 MiB。 - 条件写用真正的 `Headers`:本地 workerd 的探测确认,
onlyIf传纯对象时 R2 静默忽略条件、照样覆盖,所以适配用构造对象('Headers', …)造If-None-Match: *或If-Match: "etag"。 - `list` 的结果是纯对象,其中 `objects` 的元素是 `HeadResult` 类实例(宿主桥发句柄);未截断时
cursor键存在而值为undefined(桥写成{"$未定义":true}),适配只在truncated为真时取续标。含include时本地 R2 每页约 100 项,应用必须按cursor续列。 - 句柄:宿主事件的句柄表有 4096 项上限;
head、get、put的结果、uploaded的 Date、arrayBuffer、TextDecoder、直通的响应与头、请求体流都在用毕后释放;桶句柄不释放(同一桶的句柄在事件内复用)。测试在同一事件里重复取元数据 6000 次、列举 200 次不触及上限。 - 校验在豫言里做:桶名、键、选项 JSON(未知字段、类型、互斥、长度、保留名)、标头名与值、状态;违规抛出豫言异常,平台故障的消息保留原文。校验通过之前不创建任何宿主句柄。
- 直通不进 Wasm 内存:GET 路径只读
size、httpEtag、body,正文流交给 Response;测试对 64 MiB 对象断言arrayBuffer/text/bytes/json/blob一次也没有被调用。
验证:同一份测试在 Node(R2 为形状取自真实 workerd 的类实例模拟)与 wrangler 4.129.0 的本地 workerd(真实 R2 绑定,本地存储)里都通过,见 一致性验证/。平台观察:sha256 不符的错误文字含 (10037);键超过 1024 字节为 (10020);list 的 limit 超过 1000 为 (10022);本地 R2 先校验摘要再判前置条件;对可压缩类型且客户端接受 gzip 时,workerd 会改用分块传输而不保留 Content-Length。