实战:用 Hono 在 Cloudflare Workers 上搭一层轻量 API,封装客户的遗留系统
客户的系统通常改不了。但你仍然可以在它前面加一层干净、带类型、有认证的 API,旧代码一行都不用动。
要点速览
- Hono 在 Cloudflare Workers 上运行得很顺:用 create-hono 生成项目骨架,再用调用 Wrangler 的 npm 脚本部署。
- 封装层必须在坏请求到达客户系统之前把它拦下:检查 token、校验载荷、映射错误。
- token 放进 secret,不要把单次请求的数据存在模块级变量里,上生产环境之前打开可观测性。
到客户现场的第一周,这样的场景很常见:你的智能体或看板需要的数据,存放在一套用了十年的订单系统里。字段名全是大写,报错返回的是 HTML,没人敢动它的代码。你也不必去动。你要做的,是在它前面搭一层轻量的封装。
这正是前线部署工程师(FDE)招聘启事里写明的工作。Synapse Analytics 招聘 Forward Deployed Engineer II 的启事要求候选人构建并维护面向具体客户的集成组件,包括适配器、连接器、脚本和 API,并把反复出现的模式沉淀为产品改进。
本文用 Hono 和 TypeScript 在 Cloudflare Workers 上搭建这样一层。根据 Cloudflare 的文档,Hono 是一个轻量框架,在 Workers 上运行效果很好。为了便于阅读,下面的代码做了精简,交付给客户之前请对照官方文档核实。
要做什么,需要准备什么
举一个假设的例子:客户有一个遗留订单 API,接收 ITEM_CODE 和 QUANTITY。你的封装层对外提供 POST /api/orders 接口,接收干净的 JSON:{ sku, qty }。它检查调用方的 token,校验载荷,用自己的 token 调用遗留系统,并把下游的每一种失败都转换成统一的 JSON 错误。
你需要 Node.js 和 npm;如果要部署,还需要一个 Cloudflare 账号;以及被封装系统的 URL 和 token。在家练习的话,任何你有权调用的内部 API 都可以。
第 1 步:项目骨架和第一个接口
Hono 提供 create-hono 命令来生成项目骨架。运行下面的命令,出现提示时选择 Cloudflare Workers 模板:
npm create hono@latest orders-wrapper
cd orders-wrapper
入口文件是 src/index.ts,它把 app 作为默认处理器导出。加一个健康检查接口,确认一切正常运行:
import { Hono } from 'hono'
const app = new Hono()
app.get('/health', (c) => c.json({ ok: true }))
export default app
运行 npm run dev,打开 Wrangler 打印出的本地 URL。检查:/health 必须返回 {"ok":true}。打开 package.json,看看 dev 和 deploy 两个脚本是怎样调用 Wrangler 的。整个项目都会用到这两条命令。
第 2 步:客户的 token 放在哪里?
很多人一开始就在这里出错。Cloudflare 的最佳实践指南说得很明确:API 密钥、token 和数据库凭据绝不能出现在 Wrangler 配置文件或源代码里。要把它们放进 secret:
npx wrangler secret put UPSTREAM_TOKEN
npx wrangler secret put CLIENT_TOKEN
用 npm run dev 在本地运行时,Wrangler 会从项目根目录的 .dev.vars 文件读取 secret。这里用测试 token,不要用客户的真实 token,并把这个文件加进 .gitignore:
UPSTREAM_TOKEN=dev-upstream-token
CLIENT_TOKEN=dev-client-token
Cloudflare 还建议不要手写 Env 接口,而是用 wrangler types 生成。每次新增绑定后都运行一次:
npx wrangler types
然后把第 1 步里的 const app = new Hono() 改成下面这一行。这是替换,不是再声明一个 app:
const app = new Hono<{ Bindings: Env }>()
**检查:**在编辑器里输入 c.env.,应当能提示出正确的变量名。遗留系统的 URL(UPSTREAM_URL)不是机密,可以作为普通配置变量。
第 3 步:在坏请求到达客户系统之前拦下它
在做任何转换之前,封装层得先学会说“不”。Hono 的 Bearer Auth 中间件不要求 token 必须是 JWT,你可以用 verifyToken 函数自己检查 token:
import { bearerAuth } from 'hono/bearer-auth'
app.use('/api/*', bearerAuth({
verifyToken: async (token, c) => token === c.env.CLIENT_TOKEN,
}))
接下来是载荷校验。Zod 是为 TypeScript 设计的校验库,Hono 的 validator 中间件可以校验 json、query、header、param、cookie 和 form。安装依赖包并声明 schema:
npm i zod @hono/zod-validator
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'
const OrderInput = z.object({
sku: z.string().min(1),
qty: z.number().int().positive(),
})
**检查:**在第 4 步把 schema 挂到路由上之后,不带 Authorization 请求头的请求必须得到 401;发送 qty: -3 必须被 400 拒绝,下游代码一行都还没来得及执行。
第 4 步:翻译成遗留系统的语言,集中处理错误
到这一步才开始转换。处理器拿到已校验的数据,把字段改名以匹配遗留系统,再用遗留系统自己的 token 调用它:
import { HTTPException } from 'hono/http-exception'
const route = app.post('/api/orders', zValidator('json', OrderInput), async (c) => {
const { sku, qty } = c.req.valid('json')
const res = await fetch(`${c.env.UPSTREAM_URL}/createOrder`, {
method: 'POST',
headers: {
Authorization: `Bearer ${c.env.UPSTREAM_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ ITEM_CODE: sku, QUANTITY: qty }),
})
if (!res.ok) throw new HTTPException(502, { message: '订单系统不可用' })
return c.json(await res.json(), 201)
})
遗留系统返回 HTML 错误页时,绝不能让调用方看到。Hono 允许你抛出 HTTPException,并用 app.onError 在一处统一处理错误:
app.onError((err, c) => {
if (err instanceof HTTPException) {
return c.json({ error: err.message }, err.status)
}
console.error(err)
return c.json({ error: '内部错误' }, 500)
})
**检查:**临时把 UPSTREAM_URL 指向一个错误地址。调用方必须收到带 error 字段的 JSON,绝不能是堆栈信息或 HTML。
第 5 步:共享类型并部署
如果调用这个 API 的代码(比如智能体或前端)也由你来写,Hono 的 RPC 功能可以让服务端和客户端共享 API 规范。只需要导出 app 的类型:
export type AppType = typeof route
客户端导入 AppType,马上就能得到一个带类型的客户端。在服务端改了某个字段名,客户端会在编译时报错,而不是在客户现场出错。Cloudflare 还提到,如果之后需要用户界面,可以通过 Workers Assets 把 Hono API 和 SPA 组合在一起。
最后运行 npm run deploy。Worker 可以运行在 *.workers.dev 子域名或自定义域名上,可以从笔记本部署,也可以从任何 CI/CD 系统部署。在客户现场,第一周就把部署命令放进 CI,这样谁都不必依赖你的电脑。
让封装层在生产环境出问题的几个错误
最难发现的一个:Workers 会在多个请求之间复用 isolate。如果你把 token 或某次请求的数据存在模块级变量里,下一个请求可能会读到上一个请求的数据。凡是属于请求的东西,都必须放在处理器内部,并且从 c 获取。
其次是忽视套餐限制。Free 套餐每个请求允许 10 ms CPU 时间,每次调用最多 50 个子请求。设想一个“订单详情”接口,每个订单行都调用一次遗留系统:一个有 60 行的订单会直接超限。要么把调用合并成批量请求,要么在向客户承诺之前先核对套餐。
最后是部署之后什么都看不见。Cloudflare 把没有可观测性的生产 Worker 形容为一个黑盒。在 Wrangler 配置文件里打开它(下面是 wrangler.jsonc 的精简示例;如果你的模板用的是 wrangler.toml,请声明等价配置),然后重新部署:
{
"observability": { "enabled": true }
}
一定要在第一次有人问你“凌晨 2 点那笔订单为什么失败”之前做好这件事。
在客户现场怎样起步,简历上怎样写
在客户现场,第一项任务不是写代码,而是列清三件事:真正需要哪些接口,谁来调用,遗留系统怎样报错。在此基础上,做出一个读接口和一个写接口,当周就有东西可以演示。
简历上不要笼统地写“构建了 API”。要写清楚封装的是哪套系统,用了哪种认证,拦截了哪几类坏载荷,下游错误是怎样映射的。
这正是 FDE 招聘启事里“适配器、连接器、API”那一条所用的语言。当你封装到第三套系统,又一次看到同样的模式时,就该去告诉产品团队了。
这篇文章对你有帮助吗?
感谢你的反馈!