koa原始碼結構koa是現在我們最常用的node框架,它是一個輕量的web框架,只提供了http的協議的解析和中介軟體功能。我們要實現路由、靜態頁面託管和檔案上傳等功能均需要外掛來實現。
上圖是koa的原始碼結構,lib放著koa的核心檔案::application.js、context.js、request.js、response.js。
application.jsapplication.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: '<original node req>', res: '<original node res>', socket: '<original node 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來源:掘金著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。