← 中文资源

简体中文版本

构建可配置的 API 网关

一个完整的 API 网关:按路径前缀路由、注入鉴权头、按路由限流与超时,并输出结构化访问日志。

阅读英文原文

项目概述

本项目实现一个可配置的 API 网关:按方法与前缀把请求路由到上游服务,在边缘完成一次鉴权,按路由限流并设置转发截止时间,最后把上游失败统一成带请求 id 的错误信封。目标是让网关成为一个可靠的策略收口点,而不是只做字节转发的代理。

架构与关键决策

路由表以纯数据描述方法、前缀、上游、限流与超时,在启动时校验,因此新增路由是一次配置变更而非代码改动。鉴权在任何上游被触达之前完成,避免未认证请求占用后端资源;令牌桶按路由与主体分别计数,防止单一噪声客户端耗尽他人配额;转发使用 AbortController 绑定截止时间,慢上游以 504 结束而非长期占用连接。

实施与测试

通过可延迟、可失败的上游桩驱动真实 HTTP 请求,断言路由命中、未认证被拒、超限返回 429、超时返回 504,以及错误信封包含请求 id。由于时钟与上游桩都在测试控制之下,这些用例是确定性的。

限制与后续

  • 请求与响应体直接透传,不做过滤或重写。
  • 路由表启动时加载,不支持热更新。
  • 未实现 WebSocket 与 HTTP/2 代理。

代码示例

pnpm init
pnpm add express undici jose
pnpm add -D typescript tsx vitest
api-gateway/
├── src/routes.ts
├── src/proxy.ts
├── src/auth.ts
├── src/limits.ts
├── src/errors.ts
├── tests/gateway.test.ts
└── package.json
const routes = [
  { method: 'GET', prefix: '/users', upstream: 'http://users:8080', limit: 20 },
  { method: 'POST', prefix: '/orders', upstream: 'http://orders:8080', auth: true },
];
const { payload } = await jwtVerify(token, jwks, { issuer, audience });
req.subject = payload.sub;
if (!bucket.take(subject, route.limit)) return res.status(429).json({ error: 'rate_limited' });
const ac = new AbortController();
const t = setTimeout(() => ac.abort(), route.timeoutMs);
const upstream = await fetch(route.upstream + req.url, { signal: ac.signal });
res.status(code).json({ error, requestId: req.id });
pnpm vitest run