Koa源碼閱讀

查看Koa,version@2.7源碼,總共只有四個文件 application.jscontext.jsrequest.jsresponse.js;分別對應Koa應用入口、上下文環境、請求對象和響應對象

1、application.js

一、總覽

能夠看到這個模塊以Node內部模塊events爲父類,導出一個建立Koa應用的構造函數,這個構造函數中有幾個屬性值,咱們主要看javascript

二、屬性

  • middleware,保存中間件的數組;
  • context,上下文對象;
  • request,請求對象;
  • response,響應對象;

三、方法

3.1 listen

咱們使用中通常只傳入一個number,讓服務啓動在某個端口號。能夠看到listen內部調用的是node建立httpserver的方法。這裏主要看http.createServer方法的接受一個請求的監聽函數,同時這個函數的兩個參數分別爲req請求對象和res響應對象;
查看callback方法,java

callback() {
    const fn = compose(this.middleware);

    if (!this.listenerCount('error')) this.on('error', this.onerror);

    const handleRequest = (req, res) => {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };

    return handleRequest;
  }

開始經過compose返回鏈式處理中間件的函數,這裏查看componse方法是如何封裝的。首先先校驗了中間件數組是否符合要求,而後在返回的函數中能夠看到,經過promise.resolve將返回中間件的執行結果,中間件第一個參數是ctx,第二個參數是next,這裏能夠看到dispatch就是對應next,當咱們執行該方法就會將中間件繼續遞歸進行調用。這樣就造成Koa中那種洋蔥模型的執行順序的一個promise。node

//componse方法
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!')
  }

  /**
   * @param {Object} context
   * @return {Promise}
   * @api public
   */

  return function (context, next) {
    // last called middleware #
    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]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

而後在看handleRequest,這裏開始有點迷惑到我,他在callback裏面定義了一個函數也叫handleRequest,但後面這個只是一個函數變量的名字,裏面的handleRequest纔是咱們要分析的。爲了方便說明查看,能夠臨時改寫成如下這種寫法。api

callback() {
    const fn = compose(this.middleware);

    if (!this.listenerCount('error')) this.on('error', this.onerror);

    const xxx = (req, res) => {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };

    return xxx;
  }

在xxx函數中,首先使用createContext方法,將請求報文、響應報文等對象掛載在上下文對象上, 其中request是Koa請求對象,response是Koa響應對象,app爲當前Koa服務實例對象,req爲Node的請求對象,res爲Node響應對象。而後執行handleRequest方法,參數爲上下文和處理中間件的函數。最後返回一次請求的執行結果。這裏能夠看到返回狀態碼默認爲404數組

createContext(req, res) {
  const context = Object.create(this.context);
  const request = context.request = Object.create(this.request);
  const response = context.response = Object.create(this.response);
  context.app = request.app = response.app = this;
  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;
}
handleRequest(ctx, fnMiddleware) {
  const res = ctx.res;
  res.statusCode = 404;
  const onerror = err => ctx.onerror(err);
  const handleResponse = () => respond(ctx);
  onFinished(res, onerror);
  return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
3.2 handleRequest,createContext

如上callback中使用的兩個方法promise

3.3 use

use方法很簡單,就是將單箇中間件push進數組保存cookie

3.3 toJSON

toJSON方法引入一個only的包,打開能夠看到就是將一個對象過濾返回出一個新的只包含指定屬性值的對象app

3.4 onerror

打印錯誤日誌函數

3.5 respond

處理響應結果對象,涉及了一些buffer和stream的概念,最後返回響應結果this

2、context.js

返回就是上下文環境的對象原型

一、inspect

返回當前對象

二、toJSON

返回請求報文和響應報文和其餘一些屬性的對象

三、assert,onerror

異常處理

四、get cookies,set cookies

cookies取值和賦值的函數

五、delegate

從上面看到當前上下文原型的屬性、方法還不多,後面主要是經過delegate定義其餘屬性方法。查看delegate依賴包

function Delegator(proto, target) {
  if (!(this instanceof Delegator)) return new Delegator(proto, target);
  this.proto = proto;
  this.target = target;
  this.methods = [];
  this.getters = [];
  this.setters = [];
  this.fluents = [];
}

返回一個以Delegator爲構造函數的對象,返回的對象定義了上面幾個屬性
proto參數就是傳入的上下文原型對象,target由於這個Delegator函數調用了兩次分別是responserequest
而後Delegator原型上有幾個方法methodaccessgettersetterfluent,調用之後都把原對象返回了,因此在context.js中使用時能夠鏈式調用。以method方法爲例子:

Delegator.prototype.method = function(name){
  var proto = this.proto;
  var target = this.target;
  this.methods.push(name);

  proto[name] = function(){
    return this[target][name].apply(this[target], arguments);
  };

  return this;
};

調用該方法,首先將方法名保存進方法集合數組,而後在原對象上定義屬性方法,這時的proto就是上下文原型對象,而後target參數傳遞表示該方法具體是用的response仍是request對象上的方法。
總結就是上下文原型對象就是定義了responserequest等這些重要屬性,同時將這兩個對象中的方法,快捷的直接綁定在原型對象上,因此有些屬性或方法使用時能夠省略response和request。

3、request.js

定義一個request對象,將Node請求報文req的一些屬性和方法掛載該對象上並返回

一、header、headers都表示請求頭

二、url等等

三、就是將一些Node中的請求對象裏的屬性掛載在Koa的請求對象request上

4、response.js

和request相似,返回一個response對象,再回顧下application.js中的createContext方法;

其中request是Koa請求對象,response是Koa響應對象,app爲當前Koa服務實例對象,req爲Node的請求對象,res爲Node響應對象

其餘還包括一些響應結果返回、重定向等方法,具體沒有去細看

相關文章
相關標籤/搜索