为什么用 Worker 而不是一台服务器
Cloudflare Worker 是 isolate,不是进程——请求来了才启动,处理完不在调用之间占内存或连接。没有要保活的服务器,没有要扩的集群,也没有要选的区域;同一份脚本在每个 Cloudflare 边缘节点跑。对网关这种「接请求、挑上游、把响应流回去」的活,按请求作用域的模型很合适——但也意味着每条架构决策都得尊重 isolate 限制,传统服务器没有这些上限。
其中两条几乎决定了后面所有事:每个 isolate 128MB 内存上限,以及每个 Worker 3MiB 压缩脚本上限。
只流式,不缓冲
128MB 上限让一种常见服务器写法变得危险:先把整段上游响应读进内存再转发。LLM 补全可以任意长,对无界流做 await response.text() 自己没有上限——会一直涨到 isolate 内存耗尽。
| 反模式 | 为什么会炸 | 正确写法 |
|---|---|---|
对无界上游数据 await response.text() | 顶满 128MB isolate 内存 | 流式:return new Response(readableStream, { headers }) |
| 用模块级可变状态存请求数据 | 不同用户的 isolate 之间串数据 | 请求级状态放在 c.set(...) 或闭包里 |
| 后台工作用漂浮的 Promise | 响应返回后结果丢失、错误被吞 | c.executionCtx.waitUntil(promise) |
实践里,chat completions、Anthropic messages 和 Responses API 都返回原生 Response,包住上游的 SSE 流,按块做格式转换,而不是攒完再回放:
export default (async (c) => {
const upstream = await fetch(upstreamUrl, { method: "POST", body, headers })
// Never buffer: pipe the upstream stream straight through,
// translating SSE dialect chunk-by-chunk as it passes.
return new Response(upstream.body.pipeThrough(dialectTranslator), {
status: upstream.status,
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache" },
})
}) satisfies HonoHandler3MiB 上限逼出多 Worker 拆分
路由引擎、模型目录、Durable Objects、MCP 服务、OAuth 和整套 Hono API 挤在一份脚本里时,单个 Worker 会超过 Cloudflare 3MiB 压缩脚本上限。解法是中心加辐条:一个 web Worker 管整区路由,要么经 service binding 转给专用辐条,要么自己出页面。
| Worker | Host | Owns |
|---|---|---|
| anyrouter (web hub) | anyrouter.dev (apex) | SSR landing pages; routes and forwards everything else |
| anyrouter-api | service binding only | Hono app, executor, catalog, Durable Objects |
| anyrouter-dashboard | dash.anyrouter.dev | Dashboard app |
| anyrouter-admin | admin.anyrouter.dev | Admin app |
| anyrouter-mcp | service binding only | MCP server (JSON-RPC + OAuth) |
| anyrouter-flow | cron + service binding | Workflows and scheduled jobs |
每个辐条把自己同源的 /api/* 经 service binding 代理到 anyrouter-api,浏览器从不跨域请求——不需要 CORS,也不会给每个 Worker 多开一块公网面。
每条上游都走 AI Gateway
真正打到模型供应商时,只走三种传输形态之一,全部经 Cloudflare AI Gateway,而不是直连供应商 API:
| Transport | Reaches | How |
|---|---|---|
| Workers AI binding | First-party and partner-hosted Workers AI catalog models | c.env.AI.run(model, body, { gateway: { id }, returnRawResponse: true }) |
| Unified AI Gateway REST | CF unified-billing catalog, third-party models | One API token, cf-aig-gateway-id header, usage bills via Unified Billing |
| Per-provider AI Gateway REST | BYOK and direct providers (OpenAI, Anthropic, xAI, Groq, DeepInfra, ...) | cf-aig-authorization header plus the provider's own key in Authorization |
这层拆分对终端用户不可见——不管实际用了哪种传输,界面上都是同一枚「Cloudflare AI Gateway」徽章。背后的规则比听起来更严:每条上游都必须走 gateway.ai.cloudflare.com,文档里只有一个例外:第一方后端根本没有可代理的 HTTP origin(它用出站 WebSocket 打到用户自己的设备)。
边缘平台白送的安全原语
跑在 Workers 上,平台的加密原语就是默认能力,不是额外依赖:
crypto.getRandomValues()orcrypto.randomUUID()— neverMath.random()for tokens or idscrypto.subtlefor all cryptographic operations, including per-row encryption at restcrypto.subtle.timingSafeEqualfor secret comparison, so key checks aren't timing-attackable- Secrets live in
wrangler secret put, never hardcoded in source or committed config
显式错误处理也比有全局异常页的框架更重要——故意不用 passThroughOnException(),因为它会把真 bug 藏在静默回退后面,而不是交给 Hono 的错误处理器去记日志、修掉。
边缘换走了什么
这些都不是免费的。上面每条上限都是架构在绕开的约束,不是被消掉的约束——3MiB 上限意味着新功能得想清楚属于哪个 Worker,只流式意味着没法在到达客户端之前对完整响应做捷径后处理。换回来的是:同一份脚本在每个边缘节点同样跑,后面没有一队服务器要运维。

在自己的账号里看完整请求生命周期、模型目录和实时路由。
打开控制台