koa原理淺析

koa是現在我們最常用的node框架,它是一個輕量的web框架,只提供了http的協議的解析和中間件功能。我們要實現路由、靜態頁面託管和文件上傳等功能均需要插件來實現。

koa源碼結構


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複製代碼


koa原理淺析


來看下洋蔥模型的執行結果:

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
來源:掘金
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。


分享到:


相關文章: