koa是现在我们最常用的node框架,它是一个轻量的web框架,只提供了http的协议的解析和中间件功能。我们要实现路由、静态页面托管和文件上传等功能均需要插件来实现。
koa源码结构
上图是koa的源码结构,lib放着koa的核心文件::application.js、context.js、request.js、response.js。
application.js
application.js是koa的入口文件,它向外到处了Koa类,即函数。Koa继承了node的事件模块event,因此,我们new Koa()的实例app,可以基于事件来实现观察订阅的功能。Koa还有内置了常用的几个函数:listen、use、createContext、toJSON。
listen方法是通过http.createServer开启并监听了http服务,并且它里面还进行了中间件的合并、上下文context的初始化,并且每次请求来的中件合并、context都会重新初始化。
context.js
这部分是对中间件上下对象ctx封装和暴露,里面的重点在delegate,这个就是代理,比如我们要访问ctx.repsponse.status但是我们通过delegate,可以直接访问ctx.status访问到它。
// 暴露出来的对象const proto = module.exports = { toJSON() { return { // this.request 是通过application.js 中的createContext 方法将 reques和response对象挂载 request: this.request.toJSON(), response: this.response.toJSON(), app: this.app.toJSON(), originalUrl: this.originalUrl, req: '', res: '', socket: '' }; }, get cookies() { // .... }, set cookies(_cookies) { // .... }};// 代理 ctx.reponse 和ctx.requestdelegate(proto, 'response') .method('attachment') .method('redirect') // ...delegate(proto, 'request') .method('acceptsLanguages') .method('acceptsEncodings') // ...复制代码
request.js、response.js
这两个文件是对原生对象req和res的解析,主要是使用了Getter和setter的方式来对http协议完成解析,便于我们使用.
// requestmodule.exports = { get header() { return this.req.headers; }, set header(val) { this.req.headers = val; } // ........ get url() { return this.req.url; }, set url(val) { this.req.url = val; },}复制代码
上面的this.req 也是application.js 里面的application 方法挂载的
createContext(req, res) { const context = Object.create(this.context); // 初始化上下文ctx对象 的 request和repoonse 属性 const request = context.request = Object.create(this.request); const response = context.response = Object.create(this.response); // 保留app 实例 context.app = request.app = response.app = this; // 保留原生的 req 和 res 对象 也是 上面 request.js文件里面 写法的原因 context.req = request.req = response.req = req; context.res = request.res = response.res = res; //保留上下文 request.ctx = response.ctx = context; request.response = response; response.request = request; context.originalUrl = request.originalUrl = req.url; context.state = {}; return context; }复制代码
Koa整体流程梳理
const http = require("http");class Application { constructor() { // 用来存储 use进来的中间件函数 this.middleware = []; this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); } use(middleware) { // 将中间件加到数组⾥里里 this.middleware.push(middleware); } listen(..args){ const server = http.createServer(async(req,res)=>{ // compose 会组合所有中间件 const fn = compose(this.middleware); // 初始化上下文 const ctx = this.createContext(req, res); // 执⾏行行合成函数并传⼊入上下⽂文 await fn(ctx); // 简单处理ctx.body res.end(ctx.body); //源码中是通过handlRequest来处理请求 并在函数 reponse中对 ctx.body的各种取值情况做了判断 }) server.listen(...args); } compose() { //..... }}module.exports = Application;复制代码
中间件的原理
koa的中间件机制是一个剥洋葱式的模型,多个中间件通过use放进一个数组队列然后从外层开始执行,遇到next后进入队列中的下一个中间件,所有中间件执行完后开始回帧,执行队列中之前中间件中未执行的代码部分,这就是剥洋葱模型。koa的中间件机制,是基于async/await + Promise实现的.
compose函数实现
function compose(middlewares) { return function (ctx) { // 初始执行第一个中间件函数 return disPatch(0) function disPatch(i) { let fn = middlewares[0]; if(!fn) { return Promise.resolve() } // 返回promise return Promise.resolve(fn(ctx,function next(){ return disPatch(++i) })) } }}复制代码
koa-compose源码
function compose (middleware) { // 错误处理 if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } return function (context, next) { let index = -1 // 从第一个中间件开始执行 return dispatch(0) function dispatch (i) { // 中间件重复执行 if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i let fn = middleware[i] // 边界处理 这里的next 为undefined if (i === middleware.length) fn = next // 当fn为undefined 不执行 直接resolved if (!fn) return Promise.resolve() try { // 实际app.use 中的 next 就是 disptch函数,它对应这当前的中间件函数 return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); } catch (err) { return Promise.reject(err) } } }}module.exports = compose复制代码
来看下洋葱模型的执行结果:
async function fn1(next) { console.log("fn1"); await next(); console.log("end fn1"); }async function fn2(next) { console.log("fn2"); await delay(); await next(); console.log("end fn2"); }function fn3(next) { console.log("fn3");}function delay() { return new Promise((reslove, reject) => { setTimeout(() => { reslove(); }, 2000); }); }const middlewares = [fn1, fn2, fn3]; const finalFn = compose(middlewares); finalFn();复制代码
总结
koa的核心对象:Koa类构造函数、request、response、context都有梳理,koa的源码做了许多细节处理,这样处理有什么好处,还需和大家共同探讨
作者:hahatiger
链接:https://juejin.im/post/5e19cc3cf265da3e2b2d6dd4
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
閱讀更多 啟迪雲Tuscloud 的文章