3.2 常用中间件选型与用法


3.2 常用中间件选型与用法

「全家桶」是 Express 给人的印象,Koa 的货架恰恰相反:每一层能力都要自己挑。这是负担也是权力——本节带你过一遍新项目最常拿的五件货:路由、请求体解析、静态资源、压缩、安全头,每件讲清装法、装配位置和两个易踩的参数,最后立几条挑轮子的原则。

路由:koa-router

Koa 内核不认路径,路由是外挂层,社区事实标准是 koa-router:

npm install koa-router
const Router = require('koa-router'); const router = new Router({ prefix: '/api/v1' }); // 统一前缀,版本化从装配处开始 router.get('/users/:id', async (ctx) => { ctx.body = { id: ctx.params.id }; // 路径参数在 ctx.params }); router.post('/users', async (ctx) => { ctx.status = 201; // 创建成功用 201 ctx.body = { created: true }; }); app.use(router.routes()) .use(router.allowedMethods()); // 对未实现的方法回 405,而非 404

三个装配细节值得记住。其一,allowedMethods 不是可有可无的礼貌动作:没有它,向只实现了 GET 的路径发 DELETE 会得到 404,前端没法区分"路径不存在"与"方法不支持"。其二,prefix 在实例级设一次,好过每条路由手写前缀——改版本号时只动一处。其三,路由文件按资源拆分(users、orders 各一个 router),入口处依次 use,这个结构在 1.3 节的最小约定里埋过伏笔。

请求体解析:@koa/bodyparser

Koa 不解析请求体。JSON 与表单类请求靠官方 bodyparser:

npm install @koa/bodyparser
const { bodyParser } = require('@koa/bodyparser'); app.use(bodyParser({ enableTypes: ['json', 'form'], // 只开需要的类型,减少攻击面 jsonLimit: '2mb', // 超限直接 413,防大包攻击 onerror: (err, ctx) => { // 非法 JSON 的体面收场 ctx.throw(400, '请求体不是合法 JSON'); }, }));

解析结果从 ctx.request.body 读(ctx.body 是响应侧,别搞混)。两个高频坑:它必须挂在路由层之前——顺序反了路由拿到的一永远是 undefined(3.4 节用实验证明);它不处理 multipart——文件上传的字节流超出它的职责,交给 4.4 节的专用方案。

静态资源:koa-static

生产环境静态资源通常交给 Nginx 或对象存储,但开发环境、内部工具、小型站点用 koa-static 一层搞定:

npm install koa-static
const serve = require('koa-static'); app.use(serve('./public', { maxage: 1000 * 60 * 60 * 24 * 7, // 静态资源缓存七天,配合文件名哈希 defer: false, // 默认先于业务层;设 true 则业务层优先 }));

defer 参数值得说透:默认 false 时这一层先进、命中即截停(静态文件不需要进业务层);改成 true 后业务层先跑、全 404 才回头找文件——适合"API 与静态文件同域、路径可能冲突"的场景。缓存参数的完整语义在 5.3 节展开,这里知道 maxage 会转成 Cache-Control 头即可。

响应压缩:koa-compress

JSON 接口的响应体往往压缩后体积掉一个量级,代价是 CPU。koa-compress 一层接入:

npm install koa-compress
const compress = require('koa-compress'); app.use(compress({ filter: (type) => /json|text|javascript/.test(type), // 图片视频已压缩过,别浪费 CPU threshold: 1024, // 小于 1KB 不压,压了可能更大 br: { params: {} }, // 优先 Brotli,客户端不支持自动退回 gzip }));

装配位置很有讲究:压缩层必须在最外层——它要压的是最终响应,如果放在业务层内侧,出层阶段别人再改 body,压的就是旧内容。这个案例同时是 3.4 节"顺序改变行为"的预告。

安全响应头:koa-helmet

helmet 一层设置十几张安全响应头(CSP、HSTS、X-Content-Type-Options 等),对 API 服务几乎零成本:

npm install koa-helmet
const helmet = require('koa-helmet'); app.use(helmet({ contentSecurityPolicy: false, // 纯 JSON API 可关 CSP;有页面渲染再按需配置 hsts: { maxAge: 31536000 }, // 强制 HTTPS 一年,配合 7.3 节的 TLS 配置 }));

安全头层同样放最外层,且放在压缩层之后装配(让它成为更外的外层)——响应头与压缩互不干扰,但"安全层最后说话"的原则能避免后续层意外覆盖安全头。

一次装配到位:入口文件的样子

五层配齐后,入口文件的装配段应该长这样(顺序本身就是本节的知识点):

const Koa = require('koa'); const app = new Koa(); app.use(errorHandler()); // 1 兜底:最外层,接一切异常(见 2.4) app.use(requestId()); // 2 请求 ID:尽早生成,后面都要用 app.use(helmet()); // 3 安全头:最后说话的位置先占住 app.use(compress()); // 4 压缩:压最终响应 app.use(serve('./public')); // 5 静态:命中即截停,省掉业务开销 app.use(bodyParser()); // 6 解析:必须在路由之前 app.use(auth()); // 7 鉴权:解析完才能读 body 里的凭证 app.use(router.routes()); // 8 路由 app.use(router.allowedMethods()); app.listen(3000);

挑轮子的三条原则

货架上的轮子远不止五个,怎么挑比挑哪个更重要。一看维护活跃度:Koa 中间件的维护活跃度分化比 Express 生态明显,久未更新的轮子未必坏了(有的确实稳定),但至少说明 issue 不会有人理——上车前看一眼最近提交与 issue 响应速度。二看单一职责:一个中间件同时干解析加压缩加鉴权的,绕道走;职责单一的层才可预测、可替换、可测试。三看它对洋葱模型的用法:优秀的中间件文档会明确说"我工作在进层还是出层、我是否截停、我应该在哪个位置装配"——说不清这三件事的轮子,出了问题你连该去哪查都不知道。

💡 关键直觉:Koa 项目的依赖清单就是它的能力清单。Express 用户打开 package.json 只看到一个 express;Koa 用户打开看到的是一组显式的层——这不是麻烦,是把"服务有什么能力"从隐式约定变成了可评审的清单。

本节要点回顾

  • 路由是外挂:routes 与 allowedMethods 成对出现,prefix 实例级设一次;
  • 解析先于路由:bodyparser 挂错位置是"body 恒为 undefined"的头号原因,multipart 不归它管;
  • 压缩与安全头贴最外:压的是最终响应,安全头要最后说话;
  • 静态层会截停:defer 参数决定业务层与文件层谁先;
  • 挑轮子三原则:看活跃度、看单一职责、看它说不说清自己在半程。

货架上的轮子够用,但日志、限流这类贴合业务需求的层,社区货架上往往没有合身的——下一节回到自家工坊,把自定义中间件做成可复用的工厂。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U