banner
NEWS LETTER

从零写一个可维护的 Cloudflare Worker

Scroll down

Cloudflare Worker 适合承载轻量 API、边缘代理、Webhook 入口、静态站点的动态补充逻辑,以及需要靠近用户执行的请求处理。它不是传统常驻进程,也不应该被当成一台小型服务器来写,核心模型是:每次请求进入一个 fetch 处理函数,代码在受限运行时中完成判断、转发、读写绑定资源并返回响应。

写 Worker 的重点不在于把所有逻辑塞进一个文件,而在于先划清请求入口、路由、配置、外部资源和错误响应的边界。只要这些边界稳定,后续从单接口扩展到多接口,从简单代理扩展到鉴权、缓存、KV 或 D1 存储,代码仍然可以保持清楚。

本文结论是:先写一个明确的 fetch 入口,再按路径拆分处理函数;环境差异通过绑定和变量注入,敏感信息不写进源码;上线前用本地请求、预览环境和日志把行为确认完,再发布到生产路由。

一、问题背景

Worker 的上手门槛不高,但很多返工来自一开始没有理解它的运行方式。常见误区是把它写成 Express 风格的常驻服务、在全局变量里保存请求态、把密钥硬编码进源码,或者把代理、鉴权、缓存、错误处理混在一个大函数里。

本文只讨论“如何写一个可维护的 HTTP Worker”。范围包括项目结构、入口函数、路由拆分、环境变量、绑定资源、错误处理和发布前检查;不讨论具体价格、最新平台限制、排名对比,也不依赖某一天的实时策略变化。

二、核心思路

Worker 的入口应该尽量薄。fetch(request, env, ctx) 负责把请求标准化,随后交给路由函数或业务函数处理。业务函数只关心输入、环境资源和返回结果,不直接散落平台判断。

配置要从 env 进入代码。普通配置可以放变量,密钥应该使用 secret;KV、D1、R2、Queues 等平台资源通过绑定注入。这样同一份代码可以在本地、预览和生产环境之间切换,而不需要改源码。

路由设计优先保持显式。小型 Worker 不一定需要引入框架,用 URL 解析路径并手写分发即可;当接口数量明显增加、需要中间件、参数校验和统一错误模型时,再考虑引入路由库。

响应格式要统一。成功响应、错误响应、跨域头、缓存头和上游超时处理最好集中封装,否则接口变多后很容易出现行为不一致。

三、落地步骤

  1. 初始化项目,并确认本地开发命令可用。
1
2
3
npm create cloudflare@latest worker-demo
cd worker-demo
npm run dev

如果是在已有前端或 Hexo 项目旁边新增 Worker,建议单独放一个目录,例如 workers/api,避免把站点构建产物和 Worker 源码混在一起。

  1. 编写最小入口。先让 Worker 明确返回 JSON,并对未知路径返回 404
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);

if (url.pathname === "/health") {
return json({ ok: true, service: env.SERVICE_NAME || "worker" });
}

if (url.pathname === "/api/time") {
return json({ now: new Date().toISOString() });
}

return json({ error: "not_found" }, 404);
},
};

function json(body, status = 200, headers = {}) {
return Response.json(body, {
status,
headers: {
"cache-control": "no-store",
...headers,
},
});
}
  1. 把路由处理函数拆出来。接口超过两三个之后,不要继续扩大 fetch 函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
export default {
async fetch(request, env, ctx) {
try {
return await route(request, env, ctx);
} catch (error) {
console.error(error);
return json({ error: "internal_error" }, 500);
}
},
};

async function route(request, env, ctx) {
const url = new URL(request.url);

if (request.method === "GET" && url.pathname === "/health") {
return json({ ok: true });
}

if (request.method === "POST" && url.pathname === "/api/webhook") {
return handleWebhook(request, env);
}

return json({ error: "not_found" }, 404);
}

async function handleWebhook(request, env) {
const token = request.headers.get("x-webhook-token");
if (!token || token !== env.WEBHOOK_TOKEN) {
return json({ error: "unauthorized" }, 401);
}

const payload = await request.json();
return json({ accepted: true, id: payload.id || null });
}

function json(body, status = 200) {
return Response.json(body, { status });
}
  1. 在配置文件中声明变量和绑定。普通变量用于非敏感配置,密钥通过命令写入。
1
2
3
4
5
6
7
8
9
10
name = "worker-demo"
main = "src/index.js"
compatibility_date = "2026-07-23"

[vars]
SERVICE_NAME = "worker-demo"

[[kv_namespaces]]
binding = "CACHE_KV"
id = "replace-with-kv-id"
1
npx wrangler secret put WEBHOOK_TOKEN
  1. 访问 KV 或其他绑定资源时,把失败路径写清楚。不要默认外部资源一定可用。
1
2
3
4
5
6
7
8
9
10
11
12
13
async function getCachedProfile(userId, env) {
const cacheKey = `profile:${userId}`;
const cached = await env.CACHE_KV.get(cacheKey, "json");
if (cached) {
return cached;
}

const profile = { id: userId, name: "anonymous" };
await env.CACHE_KV.put(cacheKey, JSON.stringify(profile), {
expirationTtl: 300,
});
return profile;
}
  1. 发布前先用本地命令和预览环境验证关键路径。
1
2
3
4
5
6
npm run dev
curl -i http://127.0.0.1:8787/health
curl -i -X POST http://127.0.0.1:8787/api/webhook \
-H 'content-type: application/json' \
-H 'x-webhook-token: local-token' \
-d '{"id":"demo"}'

确认本地行为、预览环境变量和生产绑定都正确后,再执行发布命令。生产域名、路由和 DNS 配置应在发布前单独确认,避免把测试入口暴露到正式流量。

四、常见坑

  • 把密钥写进源码或提交到仓库。Worker 代码可能多人可见,密钥应通过 secret 管理。
  • 在全局变量中保存请求级数据。全局对象可以用于不可变配置或可复用客户端,但不要依赖它保存某个用户的状态。
  • 忽略请求方法。只按路径分发会让 GETPOSTOPTIONS 混在一起,后续接入浏览器跨域或 Webhook 时容易出错。
  • 错误响应不统一。同一个 Worker 里同时返回纯文本、HTML 和不同结构的 JSON,会增加调用方处理成本。
  • 本地环境和生产绑定不一致。代码本身正确,但变量名、KV 绑定名或 secret 名对不上,发布后仍会失败。
  • 代理上游时不处理超时和非 2xx。边缘代理不是简单 fetch 透传,至少要明确状态码、错误体和日志策略。
  • 缓存策略过于激进。鉴权接口、用户私有数据和调试响应通常不应被公共缓存复用。

五、检查清单

  • 文件入口只负责解析请求、分发路由和兜底错误处理。
  • 每个公开接口都明确限制了路径和 HTTP 方法。
  • 密钥使用 secret 注入,源码和配置文件中没有明文敏感信息。
  • wrangler.toml 中的变量名、KV/D1/R2 等绑定名与代码一致。
  • 成功响应和错误响应都有稳定的 JSON 结构。
  • 需要浏览器访问的接口已经处理 OPTIONS 和跨域响应头。
  • 本地、预览和生产至少各验证过 /health 或等价健康检查。
  • 日志中能定位关键失败原因,但不会输出 token、cookie 或用户隐私数据。
  • 缓存头符合数据属性,私有数据没有被公共缓存。

六、小结

Cloudflare Worker 的写法可以很轻,但不应该随意。入口函数、路由分发、环境配置、绑定资源和响应格式这几件事先定下来,后续增加接口时就不会反复拆改基础结构。

对于多数小型 API 或边缘代理,先用显式路由和少量工具函数已经足够。等到接口规模、鉴权规则或中间件需求明显增加,再引入框架或更完整的工程结构,会比一开始就堆复杂度更稳妥。

其他文章
目录导航 置顶
  1. 1. 一、问题背景
  2. 2. 二、核心思路
  3. 3. 三、落地步骤
  4. 4. 四、常见坑
  5. 5. 五、检查清单
  6. 6. 六、小结
请输入关键词进行搜索