Cap'n Web RPC:远程对象、Promise pipelining 与使用方式
Cloudflare 原文:Cap’n Web — a new RPC system for browsers and web servers
AI 参与说明(Agent:Codex):本文由 Codex 基于 Cloudflare 发布文章、官方文档与固定版本源码辅助整理,并运行最小示例。整理日期:2026-10-21。源码分析固定为
766655329fe4fe2dada67c8b4e82e3a81e38b194;示例使用 npmcapnweb@0.12.0,两者是不同快照。执行入口为 Codex Desktop;完整模型标识与 reasoning effort 未取得任务级运行记录。
场景分析补充(2026-10-22,Agent:Codex):补充交互式工作区、插件、Agent 执行器、实时协作和依赖调用的工程取舍。场景与采用优先级属于基于文中协议契约作出的工程判断,不代表官方产品保证;原有示例与源码分析快照保持不变。执行入口为 Codex Desktop;完整模型标识与 reasoning effort 未取得任务级运行记录。
资料修订(2026-11-01,Agent:Cursor):按
7666553补上Set按值传输的限制,并按143d13d说明 structured clone 传输只发送字节视图自己的区间。未重跑 Counter 示例。模型grok-4.7-high-fast,提供方 xAI,执行入口 Cursor Cloud。reasoning effort 未作为独立字段取得运行记录。
Cap’n Web 让浏览器与服务器通过 JavaScript 对象交互:方法可以返回另一个带方法的远程对象,客户端也可以把回调传给服务器。它最值得关注的能力是 Promise pipelining,让后一次调用直接引用前一次尚未返回的结果,减少连续操作中的网络等待。
这套模型适合前后端均使用 JavaScript / TypeScript、需要持续交互或细粒度授权的应用。它是 MIT 开源的协议和库,可以运行在浏览器、Cloudflare Workers、Node.js、Bun、Deno 等环境中,不要求部署在 Cloudflare。原文发布于 2025-09-22;以下接口与机制同时对照当前固定源码,不沿用发布时的包体积或实验状态描述。官方 README
| 英文术语 | 中文名称 | 简要解释 |
|---|---|---|
| RPC | 远程过程调用 | 通过调用方法来请求另一个进程执行操作 |
| object-capability | 对象能力 | 持有某个对象的引用,就获得调用该对象所公开能力的权限 |
| RpcTarget | 保留 API 名称 | 标记可以按引用传给远端、供对方调用的类 |
| RpcStub | 远程代理 | 客户端持有的代理对象,调用会被发送给真正持有对象的一方 |
| RpcPromise | 远程结果承诺 | 可以等待,也可以继续调用其未来结果的特殊代理 |
| Promise pipelining | Promise 流水线 | 先描述依赖调用,让远端使用中间结果继续执行 |
| HTTP batch | HTTP 批处理 | 把一组 RPC 消息放在一次 HTTP 请求与响应中完成 |
从返回数据到返回远程对象
A Cap’n Web call can return a live object reference, and later calls execute where that object lives. RpcTarget 与公共 API
假设服务器提供 getCounter(10),返回一个初始值为 10 的 Counter。客户端拿到的不是这个对象的完整副本,而是能调用它的 RpcStub。调用 counter.add(5) 时,服务器上的对象更新为 15;调用 counter.read() 时,服务器返回这个数值。
普通对象、数组和 Set 主要按值传输。Set 在编码任何一个元素之前,会先检查全部直接元素:元素不能直接是 Promise 或 Blob。需要保留方法与身份的类继承 RpcTarget,函数则可以按引用传递。函数也不需要传输源码:服务器收到客户端回调的 RpcStub,调用时再通过连接回到客户端执行。因此,在 WebSocket 或 MessagePort 上,双方都可以发起调用。Set 的编码、值与引用的编码
143d13d 收紧了 structured clone 传输上的字节视图。structuredClonable 和 jsonCompatibleWithBytes 会把字节交给传输层,而 structured clone 复制的是整个 backing buffer。视图没有盖住整个 buffer 时,发送侧先复制 byteOffset 起、长度为 byteLength 的那一段,再交给传输。否则 MessagePort 对端可能读到 subarray 旁边的字节,Node 里一个小 Buffer 还可能带出它所在的共享池。接收侧再复制挡不住对端读取原始消息。走 base64 的编码路径本来就只包含这一段。上文的 Counter 示例没有覆盖 Set 或这次复制;其余源码分析仍钉在 7666553。只发送视图自己的字节
适合哪些场景:从业务交互判断
我最看好的是这样一类应用:用户进入一个工作区后,会持续操作一组有身份、有权限、具有后续行为的远程对象。在线 IDE、设计工具、插件平台和 Agent 工作台都可能符合这一特征。其次是依赖关系已经确定、却因为逐次等待而变慢的调用链。
这里有两个独立收益。对象引用让接口接近业务关系,也可以承载受限权限;Promise pipelining 减少能提前描述的依赖链中的网络等待。一个插件通过同页 MessagePort 通信时,主要获得前一个收益;移动网络上的 HTTP batch 则可能主要获得后一个收益。选型时应分别判断,不要求每个场景同时用到所有能力。
交互式工作区:反复操作同一组资源
例如在线 IDE 中的 Workspace、Document、Terminal,或设计工具中的 Project、Canvas、Layer。用户打开资源后,会继续修改、保存、监听变化或执行命令,接口天然具有层次和生命周期。
下面是接口设计草图,不属于后面的 Counter 服务;权限、持久化和变更格式由应用实现:
const workspace = api.openWorkspace(workspaceId);
const document = workspace.openDocument(documentId);
const revision = await document.applyChanges(changes);
这里的价值是,workspace 已经代表某个访问范围,document 又把后续操作收敛到一份文档。服务端在发放对象时核验权限,并通过所暴露的方法限制行为;客户端后续不需要每次都重新描述完整资源路径。依赖图能提前确定时,这几次调用也能使用 Promise pipelining。对象与依赖调用
适合持续使用的对象接口,不等于把所有业务状态放进连接。文档内容与修订号仍应保存到持久存储;Document 的 RpcStub 是访问它的一种方式。连接断开后,应能凭稳定的工作区与文档 ID 重新打开。权限撤销也需要对象内部检查或其他明确机制,不能假定取得过一次引用就永远有效。session 与重建
如果一个后台只是“查询用户列表、提交一张表单”,而用户对象没有后续行为,普通数据接口通常更直接。我不会仅为了少写几个 fetch(),让简单 CRUD 承担远程引用、释放和断线恢复的成本。
插件、iframe 与 Web Worker:按对象交付有限能力
假设图片编辑器允许第三方插件运行。一个色彩分析插件只需要读取当前图片,一个滤镜插件可以提交当前图片的修改;它们都不需要取得整个账户、所有文件和宿主的完整 API。
Cap’n Web 可以让宿主给插件一个较窄的对象,例如只提供 readPixels(),或额外提供 applyFilter()。这比让所有插件访问同一个全能入口更容易看清权限范围。调用返回值、错误和回调也沿用统一 RPC 模型,减少手写 postMessage 消息分派的工作。MessagePort 接入
我认为这类场景中,接口与权限表达的收益往往比延迟收益更重要。同一浏览器中的消息传递不应套用跨地域网络的性能估算。
object-capability 只能约束经这些对象提供的能力,插件已有的其他权限仍需浏览器隔离策略管理。宿主需要先建立适当的 iframe sandbox、origin 或 Worker 边界,再确认 MessagePort 来自预期发送方。官方要求新建 MessageChannel 并转交其中一个 port,不能直接把 Window 当作 RPC port。把一个全能对象交给插件,也不会自动获得最小权限。
Agent 执行器:受限工具对象与可组合操作
这里值得采用的通常是 Agent 的工具执行层。例如,一次任务只允许读取某个项目、修改指定输出目录,或操作某个浏览器会话。可信执行器可以持有相应的受限对象,模型提出的工具请求由执行器转成 RPC,而不必把全局凭据交给每段生成代码。
具体收益是:资源边界可以由对象承载,工具返回的资源还能继续操作,进度可以通过回调报告。前提是底层实现真正检查目录、资源归属和可执行操作;预算、审批、审计与撤销也需要应用维护。Cap’n Web 本身不提供代码隔离、Agent planner 或模型策略。object-capability 的授权模式
还需要区分两种调用链:
- 执行器已知后续操作:例如创建一个临时工作区,再在该工作区运行预先确定的检查;在任务句柄和依赖能提前表达时,可以使用 Promise pipelining。
- 模型必须先阅读结果:例如先搜索代码,模型判断应修改哪个文件,再决定下一次工具调用。后续决定尚未产生,这段等待仍然存在。
因此,我更看好 Cap’n Web 在“Agent 工作台内部的工具与资源连接”中的价值。把一次普通聊天接口换成 Cap’n Web,不会直接加快模型推理,也不会自动降低多轮工具规划的成本。真实的多层能力组合可以继续阅读 Cloudflare OS 分析。
实时协作:对象会话与双向通知
共享画布、协作文档、房间式应用常有两类交互:客户端向某个资源提交操作,服务器把新状态或其他用户的活动通知客户端。返回 Room 或 Document 对象,再接收客户端的回调对象,可以把两类交互放在同一套 API 中,省去另写一套独立的消息分派协议。
但协作产品还必须回答“两个用户同时修改时,结果怎样确定”。Cap’n Web 不提供 CRDT、OT 或冲突合并;这些规则属于文档与协作模型。客户端回调也不构成可靠事件日志:断线期间发生的变化,需要应用用事件 ID、历史存储和重新订阅来补齐。官方可恢复订阅说明
我的采用边界会放在文档操作、会话管理和通知 API。高频光标、逐帧画面或大量细碎事件仍需按流量特征设计合并、节流和发送速率;一次方法调用写起来简单,不意味着适合为每个像素或每一帧都发一次 RPC。
在 Cloudflare 上,可以将同一房间或文档的连接路由到对应 Durable Object,由应用在那里维护协调与持久数据,Cap’n Web 负责对外的调用关系。两层的职责应分别设计,不把 RPC session 当成 Durable Object 存储。Workers 与 Durable Objects 接入
高延迟下的依赖调用:提前表达整条操作链
移动网络和跨地域访问中,常见损耗是客户端一直在等中间结果。假设一条链有三次依赖调用,每次网络往返为 100 ms,忽略服务器计算、传输大小与连接建立,逐次等待约花 300 ms;若能合为一次往返,网络等待约为 100 ms。这只是解释机制的估算,不能作为实际性能指标。
HTTP batch 很适合这种一次性操作:不用维持长期连接,仍能在同一请求内使用之前调用的结果。但完整依赖必须能在第一次等待前表达,服务器的工作量和数据库查询次数也不会因此自动减少。HTTP batch 契约
比较时还应加入一个现实的基准:服务端已有的聚合接口。如果一个 createAndConfigureProject() 已经在一次请求内完成全部工作,Cap’n Web 不会再把一次往返减少成零。它可能提供的额外价值是让客户端组合已有方法,而不用为每种固定组合都新建一个聚合入口;是否值得,要与接口治理和限流成本一起衡量。
同理,如果主要耗时来自数据库扫描、模型推理或大文件处理,或客户端与服务器的 RTT 已经很低,应先处理真正的瓶颈。网络往返与数据库 N+1
持久任务:提供操作入口与进度界面
视频转码、代码构建或数据导入可以用 Cap’n Web 提供启动、查询、取消及当前进度接口。任务本身则需要独立保存状态、处理重试,并能通过稳定的 job ID 重新找到。
An RPC session is temporary; a durable job must remain recoverable after that session disappears. 如果用户关闭页面后任务仍应继续,任务生命周期就不能只依赖当前 WebSocket 或某个返回的 RpcStub。重连后应重新取得任务对象,再从已处理的进度位置恢复观察。断线后的设计要求
我的选择会是:用它组织交互界面与任务控制,持久执行交给已有的工作流或任务系统。不能仅凭回调就承诺任务可靠执行、消息不丢或失败后自动恢复。
采用优先级与不优先采用的情况
| 场景 | 我的采用倾向 | 收益成立的前提与主要成本 |
|---|---|---|
| 在线 IDE、设计工具、交互式工作区 | 优先评估 | 资源具有后续行为;能管理对象权限、释放与重连 |
| iframe 插件、Web Worker 模块 | 优先评估 | 有明确的上下文隔离,能设计较窄的对象接口 |
| Agent 的工具执行与资源访问层 | 值得评估 | 执行器可信、资源边界真实有效;已知调用链才适合提前组合 |
| 实时协作的操作与通知接口 | 适合做通信层 | 一致性、断线补发和高频事件处理由应用实现 |
| 高 RTT 下的一次性依赖链 | 有条件采用 HTTP batch | 可提前描述依赖;与现有聚合接口比较收益 |
| 简单 CRUD、单向查询和表单提交 | 通常沿用现有接口 | 远程对象和双向调用收益有限,避免引入额外生命周期成本 |
| 面向不受控客户端的公共多语言 API | 通常不作为默认入口 | 先满足公开契约、SDK、版本兼容与 HTTP 缓存等要求 |
| 持久工作流、可靠事件分发 | 仅用于交互与控制接口 | 执行状态、幂等、重试及事件历史需要其他机制 |
我的实际接入顺序会从一个边界清楚的模块开始,例如文档编辑会话或插件桥接,保留已有公开数据接口。验证时重点看:是否减少了手写消息分派与重复资源参数,是否真的减少了端到端等待,以及断线后能否用稳定 ID 重建对象。只看到代码行数减少,或只测本机 Counter 的返回速度,还不足以判断整个应用值得迁移。
公共 API 的取舍也不意味着协议从原理上排斥其他语言。其他语言可以实现兼容 peer;当前官方库的主要使用环境是 JS/TS,需要自己承担的客户端与兼容性工作仍应计入成本。无需独立 IDL,也不免除旧客户端兼容与滚动部署的责任。多语言边界、API 演进
一个可以运行的例子
下面使用 Node.js 24 和 capnweb@0.12.0,只监听本机。getCounter() 返回远程对象,add() 返回同一个对象,再由 read() 取值。示例不涉及认证或持久化。
在一个空的实验目录安装依赖:
npm init -y
npm install --save-exact capnweb@0.12.0
保存为 server.mjs:
import { createServer } from 'node:http';
import { RpcTarget, nodeHttpBatchRpcResponse } from 'capnweb';
class Counter extends RpcTarget {
#value;
constructor(start) {
super();
this.#value = start;
}
add(delta) {
if (typeof delta !== 'number' || !Number.isFinite(delta)) {
throw new TypeError('delta must be a finite number');
}
this.#value += delta;
return this;
}
read() {
return this.#value;
}
}
class Api extends RpcTarget {
getCounter(start) {
if (typeof start !== 'number' || !Number.isFinite(start)) {
throw new TypeError('start must be a finite number');
}
return new Counter(start);
}
}
let posts = 0;
const server = createServer(async (request, response) => {
if (request.url !== '/api') {
response.writeHead(404).end();
return;
}
if (request.method === 'POST') console.log(`POST /api #${++posts}`);
try {
await nodeHttpBatchRpcResponse(request, response, new Api());
} catch (error) {
console.error(error);
response.writeHead(500).end();
}
});
server.listen(8787, '127.0.0.1', () => {
console.log('RPC: http://127.0.0.1:8787/api');
});
保存为 client.mjs:
import { newHttpBatchRpcSession } from 'capnweb';
const api = newHttpBatchRpcSession('http://127.0.0.1:8787/api');
const counter = api.getCounter(10);
const updated = counter.add(5);
const result = await updated.read();
console.log(result);
先在一个终端执行 node server.mjs,再在另一个终端执行 node client.mjs。客户端输出 15,服务器新增一行 POST /api #1。三个有依赖的 RPC 只使用一次 HTTP 请求与响应。本文在 Node.js 24.19.0 上运行得到这一结果。Node HTTP 适配入口
counter 和 updated 都是 RpcPromise。客户端还没有收到真实对象,就已经能描述后续操作;服务器会先获得 Counter,再执行 add(5),最后执行 read()。
Promise pipelining 怎样节省往返
Promise pipelining removes intermediate network waits while preserving dependencies between calls. 官方 Pipelining Tour
如果通过持续连接逐次 await getCounter()、await add()、await read(),客户端需要三次等待回复。上面的写法提前描述完整依赖关系,HTTP batch 将这些消息一起发送。连接建立和服务器计算仍需要时间,减少的是中途把结果带回客户端、再发下一次请求的等待。
sequenceDiagram
participant C as Client
participant S as Server
Note over C,S: 下列四条请求消息放在同一个 HTTP POST 中
C->>S: push 1:getCounter(10)
C->>S: push 2:结果 1 的 add(5)
C->>S: push 3:结果 2 的 read()
C->>S: pull 3:请求取回最终结果
Note over S: 按依赖取得 Counter、加 5、读取数值
S-->>C: resolve 3:15
在示例的实际 HTTP 请求体中,可以看到四行 JSON:
["push",["pipeline",0,["getCounter"],[10]]]
["push",["pipeline",1,["add"],[5]]]
["push",["pipeline",2,["read"],[]]]
["pull",3]
这是按行传输的四条 JSON 消息,不是一个 JSON 数组。ID 0 代表初始 API,随后三次调用的结果分别取得 ID 1、2、3。后续消息引用这些 ID,就不必先收到对象;最终响应为 ["resolve",3,15]。
需要区分“发起调用”和“取回结果”。Calling a method sends or buffers a push; awaiting its RpcPromise requests the result with pull. WebSocket 的 push 可以立即发送;HTTP batch 的 transport 先收集消息,再在下一宏任务发出 POST。不能把两种模式都理解为“只有 await 才联网”。sendCall 与 sendPull、BatchClientTransport
如果客户端必须先读出某个结果、用本地 if 判断,再决定调用哪个方法,这一步仍然要等待。Promise pipelining 也不提供数据库事务的原子性:链中某个后续操作失败,不表示此前的业务写入会回滚。
架构与源码入口
各 transport 共用同一套对象、结果和消息模型。可以从下表按问题定位,而不必先通读整个仓库。
| 模块 | 职责 | 对应能力与入口 |
|---|---|---|
src/index.ts | 对外 API 与运行时适配 | RpcTarget、RpcStub、RpcPromise 与 Workers 入口 |
src/core.ts | 代理属性路径、方法调用与应用值交付 | PROXY_HANDLERS、RpcPromise |
src/rpc.ts | session、引用表、消息收发与错误传播 | RpcSessionImpl |
src/serialize.ts | 值编码与调用表达式求值 | Devaluator、Evaluator |
src/batch.ts、src/websocket.ts、src/messageport.ts | 通过不同连接传输消息 | HTTP batch、WebSocket、MessagePort |
src/map.ts | 记录并重放逐元素 RPC 操作 | sendMap、applyMap |
支撑这些模块的核心数据包括:
| 数据 | 保存什么 | 在例子中的作用 |
|---|---|---|
| RpcStub 与 RpcPromise | 指向调用目标的 hook,以及属性路径或未来结果 | 让 counter.add 表示一次远程方法调用 |
| RpcPayload | 参数或返回值,以及其中引用的所有权 | 传递 10、5、Counter 引用与最终 15 |
| import table | 本地持有的远端引用、等待结果与引用计数 | 记录客户端等待的结果 ID 3 |
| export table | 向对端暴露的本地目标、结果与引用计数 | 保存初始 Api、Counter 和后续结果 |
沿 getCounter(10) 到 15 的真实路径看:PROXY_HANDLERS.apply() 收到方法调用,封装 RpcPayload;RpcImportHook.call() 进入 sendCall(),编码 pipeline 并发出 push。服务端 readLoop() 收到消息后,用 Evaluator 求值并把结果放入 export table。下一条消息中的 ID 引用由此找到前一结果,最终通过 RpcPayload.deliverCall() 在参数准备好后调用应用方法。代理与调用、readLoop、deliverCall
最后一次 await 进入 RpcPromise 的 then() / pullPromise() 路径,要求远端传回结果。服务端收到 pull 后等待对应值,发送 resolve 或 reject;客户端按 import ID 找回等待项,交给 JavaScript 调用方。因此,中间对象可以始终留在服务器,客户端只接收最后的数值。pullPromise、结果解析与错误传播
object-capability 怎样表达权限
Counter 示例展示了对象引用,但没有权限设计。实际应用可以只公开 authenticate(token),认证成功后返回绑定某个用户的 AuthenticatedApi,再由它返回该用户可编辑的文档对象。
Holding an object reference grants access to the operations that object exposes. 客户端不能仅凭自己构造一段对象数据就取得某个会话的有效引用;服务端必须在允许访问时把 capability 交给它。不同用户取得不同对象,也就可以具有不同的操作范围。官方安全说明
这不代表所有授权检查都能取消。对象内部仍需维护业务约束;权限撤销、过期、资源归属与是否允许转授,都要按应用场景设计。官方建议通过 RPC 方法在连接内提交 token 并取得授权对象,不把整个连接的权限隐含在调用顺序里。
TypeScript 类型在运行时会被擦除。示例中的 typeof 和 Number.isFinite() 才真正拒绝错误参数;实际 API 可以使用既有 schema 校验库,或选择官方配套的 capnweb-validate。后者需要单独接入,不能把“无需独立 schema 编译”理解成“输入自动可信”。另外,TypeScript 的 private 不会在运行时隐藏方法;确需私有成员时使用 JavaScript # 语法。Types are not validation
列表的 map 做了什么
假设另一个 API 提供 listUserIds() 和 getUserProfile(id),可以这样描述逐元素调用;这是接口示意片段,不属于前面的 Counter 服务:
const profiles = await api.listUserIds().map(id => api.getUserProfile(id));
The map callback runs once on the calling side to record RPC operations; the peer replays those operations for each element. 客户端先用占位值运行同步回调,记录“取得 ID 后调用 getUserProfile”的操作,再把记录交给远端逐项执行。map 的机制与限制
因此,不能把它当成任意 JavaScript 的远程执行环境。回调不能 await,也不能依靠尚未取得的 ID 做本地条件判断或字符串拼接;普通本地副作用只会在记录时发生一次。回调捕获的 RpcStub 会交给对端,也必须在相应信任边界内使用。
减少客户端网络往返也不会自动合并数据库查询。如果每个 getUserProfile() 都单独查数据库,服务器仍然可能执行 N+1 次查询;需要合并读取时,应提供接受一组 ID 的方法并在业务层批量查询。官方 N+1 讨论
HTTP batch、WebSocket 与 Workers 接入
| 方式 | session 持续时间 | 适合的交互 |
|---|---|---|
| HTTP batch | 一次 HTTP 请求与响应 | 一次性读取、提交及其中的依赖调用 |
| WebSocket | 对应连接的生命周期 | 持续交互、服务器调用客户端回调 |
| MessagePort | 对应 port 的生命周期 | 页面与 iframe、Web Worker 之间的对象调用 |
HTTP batch 收到结果后就结束,不能继续使用从该 batch 获得的对象引用。WebSocket 断开后,旧 RpcStub 也会失效;应用需要建立新 session、重新认证并取得新的引用。session 的引用表不是持久化数据库;长任务进度、断线续传位置与重复提交的处理需要单独设计。持续 session 中不再使用的引用也应按 disposal 规则释放。Sessions & Reconnection
本文同时实测了两个边界:Counter batch 完成后再次调用旧引用会被拒绝;新 batch 传入字符串 getCounter('10'),最终等待操作会收到服务端的 start must be a finite number 错误。
Cloudflare Workers 可以复用同样的业务类。下面是适配片段,需要把前面的 Counter、Api 类放在同一模块中,并将 Node HTTP server 部分替换为:
import { newWorkersRpcResponse } from 'capnweb';
export default {
fetch(request) {
if (new URL(request.url).pathname !== '/api') {
return new Response('Not found', { status: 404 });
}
return newWorkersRpcResponse(request, new Api());
},
};
newWorkersRpcResponse() 可以处理 HTTP batch 和 WebSocket upgrade;浏览器长期连接对应 newWebSocketRpcSession('wss://example.com/api')。这部分对照官方适配入口,实际运行结果来自前面的 Node HTTP 示例。Workers 入口
Cap’n Web 与原生 Workers RPC 属于不同接入方式:前者提供浏览器等环境可用的库与线协议,后者由 Workers 平台提供 Service bindings 等运行时接口。两者可以配合使用,但安装 capnweb 不会自动获得某个 Worker binding 的权限。
与其他方案怎样取舍
| 方案 | 主要表达方式 | 选型时值得比较的差异 |
|---|---|---|
| REST | 资源、URL 与 HTTP 方法 | HTTP 缓存与通用客户端工具有成熟支持;Cap’n Web 更直接表达对象方法及依赖操作 |
| tRPC | TypeScript 类型约束的过程调用 | 同样无需独立 IDL;Cap’n Web 的重点是远程对象、函数引用与依赖调用的 Promise pipelining |
| JSON-RPC | 方法名与数据参数 | 通常不包含对象引用及其生命周期、对尚未返回结果的引用机制 |
| GraphQL | 查询语言描述的数据图 | Cap’n Web 用对象调用构造依赖关系,但没有自动数据库批量加载或查询成本分析 |
| Cap’n Proto | schema、代码生成与二进制协议 | 适合多语言场景;Cap’n Web 更贴近 JavaScript,两者线协议不兼容 |
Batching 合并的是参数已经确定的独立调用;Promise pipelining 还能表示后一次调用依赖前一次结果。这是比较 tRPC 与 Cap’n Web 时更有用的区分,不能仅根据“都支持批处理”判断能力相同。官方方案比较
如果应用本来就有会话对象、文档编辑对象、客户端回调,而且前后端主要使用 JS/TS,Cap’n Web 值得做小规模接入验证。如果主要目标是公开多语言 API、标准 HTTP 缓存或声明式数据查询,应先评估对应方案的客户端生态和运维方式。
对外服务还需要按业务操作限制开销。一个 HTTP 请求可以包含很多调用,嵌套 .map() 也可能放大工作量;仅统计请求数的网关限制不等于限制了昂贵操作。应在应用层限制批量规模、操作次数或业务配额,并按 transport 设置载荷上限。应用限流与消息大小
关联阅读
- Cloudflare 文档导航:按 Workers、Durable Objects 等产品继续阅读。
- Workers:运行时 API、Bindings 与执行模型:区分运行时接口、资源授权与控制面 API。
- Cloudflare OS:产品形态、Gadgets、Gatekeepers 与 Workers 架构:看 Cap’n Web 在真实应用中怎样逐步授予对象能力。
一手资料
- Cloudflare 发布文章:设计动机、基本用法与 2025 年发布时的说明。
- 固定源码与文档:本文机制分析的依据,包含
packages/docs/。 - capnweb@0.12.0 release:本文 npm 示例使用的发行版本。
- Wire protocol:消息、表达式、引用与生命周期约定。