尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Egg中间件

Egg中间件 下面按“它是什么 → 什么时候加载 → 什么触发 → 原理 → 如何开发”来说明 Egg 的HTTP 中间件。1. Egg 中间件是什么Egg 基于 Koa所以 Egg 的中间件本质上就是Koa 中间件asyncfunctionmiddleware(ctx,next){// 请求进入时执行awaitnext();// 暂停当前中间件把请求交给后面的中间件或业务接口处理并等待它们处理完成。// 后续中间件、路由、Controller 执行完后再回来执行}它主要用于处理请求链路中的通用逻辑例如日志记录鉴权错误处理请求体解析响应压缩跨域处理请求耗时统计统一响应包装在 Egg 里一个完整的中间件通常是一个工厂函数module.exports(options,app){returnasyncfunctionmyMiddleware(ctx,next){awaitnext();};};其中options来自app.config[中间件名]app是当前 Egg Application 实例返回值必须是一个 Koa 风格的中间件函数(ctx, next) {}2. 中间件什么时候加载Egg 的普通 HTTP 中间件是在应用启动阶段加载的不是请求来了才读取文件。在当前仓库代码里App Worker 的加载顺序大致是loadApplicationExtend loadRequestExtend loadResponseExtend loadContextExtend loadHelperExtend loadCustomLoader loadCustomApp loadService loadMiddleware loadController loadRouter也就是说先加载扩展、插件、配置等加载app.js/app.ts加载 Service加载 Middleware加载 Controller加载 Router。对应源码位置egg/packages/egg/src/lib/loader/AppWorkerLoader.tsegg/packages/core/src/loader/egg_loader.ts中间件加载时发生了什么Egg 的loadMiddleware()会做几件事扫描各个 load unit 下的app/middleware目录例如应用自己的app/middleware插件的app/middleware框架内置的app/middleware把中间件工厂函数加载到app.middlewares同时也暴露到app.middleware所以你在路由里常见这种写法app.router.get(/foo,app.middleware.auth(),controller.foo.index);按顺序读取app.config.coreMiddleware.concat(app.config.appMiddleware)也就是框架 / 插件核心中间件 → 应用中间件对每个中间件执行工厂函数constmwcreateMiddleware(options,app);包装enable、match、ignore等通用配置。调用app.use(mw);把中间件加入 Koa 的中间件栈。最后 Egg 会把 Router 中间件追加到最后app.use(app.router.middleware());所以全局中间件通常会在路由和 Controller 之前执行。3. 什么触发中间件HTTP 请求触发中间件。当请求进入 Egg 应用时大致流程是Node.js HTTP Server ↓ app.callback() ↓ 创建 ctx ↓ koa-compose 组合 app.middleware 数组 ↓ 按顺序执行中间件 ↓ router middleware ↓ 匹配路由 ↓ 路由级中间件 ↓ Controller ↓ 响应返回中间件按洋葱模型回退也就是说中间件不是被你手动调用的而是请求进入应用后由 Koa/Egg 的请求处理链自动触发。全局中间件如果通过配置启用// config/config.default.jsmodule.exports{middleware:[gzip],};那么这个中间件会参与每一次 HTTP 请求除非配置了match/ignore。路由级中间件如果只挂载在某个路由上app.router.get(/admin,app.middleware.auth(),controller.admin.index);那么只有请求匹配/admin这条路由时才会触发这个中间件。4. Egg 中间件的核心原理是什么核心原理是 Koa 的洋葱圈模型和koa-compose。假设有三个中间件app.use(mw1);app.use(mw2);app.use(mw3);每个中间件都写成asyncfunctionmw(ctx,next){console.log(before);awaitnext();console.log(after);}请求执行顺序是mw1 before mw2 before mw3 before router / controller mw3 after mw2 after mw1 after这就是“洋葱模型”。await next()的作用next()表示把控制权交给下一个中间件。例如module.exports(){returnasyncfunctionauth(ctx,next){if(!ctx.session.user){ctx.status401;ctx.bodyUnauthorized;return;}awaitnext();};};这里如果没有登录就直接return后面的中间件、路由和 Controller 都不会执行。所以中间件可以放行请求await next()截断请求不调用next()在后续处理完成后修改响应await next()之后处理ctx.body5. 如何开发一个 Egg 中间件以一个请求耗时统计中间件为例。第一步创建中间件文件// app/middleware/request_time.jsmodule.exports(options,app){returnasyncfunctionrequestTime(ctx,next){conststartDate.now();try{awaitnext();}finally{constcostDate.now()-start;// 记录每次请求耗时便于排查慢接口和性能问题。app.logger.info([request-time] %s %s %sms,ctx.method,ctx.url,cost);if(options.headerName){// 将耗时写入响应头方便本地调试或网关侧观测。ctx.set(options.headerName,String(cost));}}};};注意文件名request_time.js会被转换成中间件名requestTime。第二步在配置中启用// config/config.default.jsmodule.exports{middleware:[requestTime],requestTime:{headerName:x-response-time,},};这里middleware:[requestTime]表示启用这个应用中间件。requestTime:{headerName:x-response-time,}会作为options传入module.exports(options,app){}第三步使用通用配置控制生效范围关闭中间件module.exports{requestTime:{enable:false,},};只匹配某些路径module.exports{requestTime:{match:/api,},};表示只对/api前缀的请求生效。忽略某些路径module.exports{requestTime:{ignore:/health,},};表示/health请求不会经过这个中间件。使用函数动态判断module.exports{requestTime:{match(ctx){// 只对带 debug 参数的请求启用耗时响应头。returnctx.query.debug1;},},};注意match和ignore不能同时配置。6. 如何只在某个路由使用中间件如果不想全局启用可以不写config.middleware而是在路由中手动挂载// app/router.jsmodule.exportsapp{constauthapp.middleware.auth({role:admin,});app.router.get(/admin/users,auth,app.controller.admin.users);};这种方式下auth不会全局执行只有请求命中/admin/users时才会执行适合鉴权、权限校验、局部限流等场景。7. 框架 / 插件中间件和应用中间件的区别Egg 里主要有两类全局中间件1. Core Middleware框架或插件提供的中间件配置在app.config.coreMiddleware当前框架默认包含类似[meta,siteFile,notfound,bodyParser,overrideMethod]它们会比应用中间件更早加载。2. App Middleware应用自己配置的中间件来自config.middleware在加载配置时Egg 会把它转换成app.config.appMiddleware最终加载顺序是coreMiddleware → appMiddleware → routerMiddleware也就是框架/插件中间件 → 应用中间件 → 路由分发8. 开发中间件时的注意事项1. 一定要返回函数错误写法module.exports(options,app){// 忘了 return middleware};正确写法module.exports(options,app){returnasyncfunctionmyMiddleware(ctx,next){awaitnext();};};2. 不要忘记await next()如果你希望请求继续到后续中间件和 Controller必须调用awaitnext();否则请求会被当前中间件截断。3. 适合在await next()前做请求前置处理例如returnasyncfunctionauth(ctx,next){// 请求进入 Controller 前校验身份。if(!ctx.session.user){ctx.status401;return;}awaitnext();};4. 适合在await next()后做响应后置处理例如returnasyncfunctionresponseWrapper(ctx,next){awaitnext();// Controller 执行完成后统一包装响应。ctx.body{success:true,data:ctx.body,};};5. 当前 Egg 代码已不支持 generator 中间件旧式写法类似function*middleware(next){}当前代码中如果检测到 generator middleware会直接抛错。应使用 async function。9. 一句话总结Egg 中间件就是 Koa 风格的请求处理函数。它在应用启动时从app/middleware加载根据coreMiddleware和middleware配置加入 Koa 中间件栈每次 HTTP 请求进入app.callback()后被koa-compose按洋葱模型触发执行开发时只需要编写(options, app) async (ctx, next) {}然后在配置或路由中挂载即可。AGENTS.md 检查结果已按仓库要求检查是否需要更新AGENTS.md。本次是对 Egg 中间件机制的解释性问答没有产生新的稳定开发约定、架构规则或可复用流程变更因此本次未发现需要更新 AGENTS.md 的稳定信息。
返回列表