Koa源碼閱讀

Koa源碼

繼著上文的Co源碼以及與Koa的深入理解,開始學習下Koa是怎么寫的。我看的版本是1.1.2的。

從package.json里面看,Koa的入口是application.js,于是先看這個js。

application引用的模塊

先從最開始引入的一些模塊開始理解。

  • debug:最開始引了一個外部的debug模塊。這是個好東西,只要在運行的時候使用DEBUG=..就可以了進行代碼的調試了,如下圖,如果有多個的話可以用逗號分隔開。會有比較好看的風格,主要是用來替換console.log的,看上去更專業點。

<img alt="debug使用" width='700px' src="pics//debug.png" />

  • events:然后引用了nodejs的內置模塊events,將application的prototype設置了下
  • composition:外部模塊,試驗性質。是可以將傳入的函數包裝成一個promise的。
  • 主要是支持包括es7的async語法的,如果設置了this.experimental,就會使用這個來執行,否則的話使用co.wrap方法。
  • koalization是不是打開了experimental?
  • on-finished:外部模塊,是在response結束或者失敗的時候觸發的,如果失敗了的話會帶個error進來。
  • response,request,context:3個內部代碼塊,都是重新對req,res和context進行了封裝。
  • koa-compose:外部模塊,是個核心方法,將所有的中間件嵌套為循環的genreator,然后就可以交給co去wrap一下變成promise,然后就可以直接執行了。
  • koa-is-json:一個小的外部模塊,用來檢測body是不是json
  • statuses:外部模塊,簡單的http狀態碼的相互對應而已
  • cookies:外部模塊,進行cookie的管理
  • accepts:外部模塊,用來對請求頭的accepts進行管理,處理
  • assert:外部模塊,用來寫單元測試的
  • stream:內置模塊,用來處理流式的數據的
  • http:內置模塊,用來createServer的
  • only:外部模塊,用來返回一個新對象,包含只在傳入的白名單中的屬性
  • co:外部模塊,前面已經研究過的模塊,不再詳細記錄,參見Co

至此,使用的模塊已經清楚,開始看下整個結構。

application的結構設計

Application是被拋出去的總入口,是個構造函數,他的作用是

  • 區分傳入的環境參數
  • 初始化存使用的中間件middleware,一個空數組。
  • 使用自己的包裝過的3個內部代碼來初始化上下文,req和res。
function Application() {
  //這一句的作用挺精髓的,因為application是一個構造函數,如果沒有new調用的話,就return一個new的
  //這里返回的是new Application,而不是new Application(),是不是有些奇怪,其實這兩種寫法在沒有參數時沒有區別,在有參數時,必須加上括號~
  if (!(this instanceof Application)) return new Application;
  //這句就是處理環境參數,默認為開發,代碼里可以根據env的值來決定訪問哪里的數據庫,或者錯誤log的記錄地點等等
  this.env = process.env.NODE_ENV || 'development';
  //被忽略的子域名位數,比如ppe.b.dianping.com,他的默認子域名就為['ppe','b'],如果設置為3,子域名就為['ppe']
  //設置了這個值決定了我們在this.subdomains數組里面可以訪問的值
  this.subdomainOffset = 2;
  //存中間件,下面的app.use方法直接push就好了
  this.middleware = [];
  //這個東西也不知道是啥。。
  //tudo:X-Forwarded-Proto??
  this.proxy = false;
  //這三個東西就是作者包裝后的上下文,req,res
  this.context = Object.create(context);
  this.request = Object.create(request);
  this.response = Object.create(response);
}

app是Application的原型對象。

強行用emitter的原型來替換了application的原型。

//這樣子就繼承了事件流
//然后下面的代碼this.on('error',fn)來監聽
//this.emit就可以觸發
//這里想到了點評張老師的cortex里面的模塊class也支持implement events這個東西,然后就去看了下他是怎么在browser端支持emitter的,原來是引用了npm的一個模塊events
Object.setPrototypeOf(Application.prototype, Emitter.prototype);

然后對app進行原型上的包裝。

  • app.listen:來createServer的,將this.callback添加到了request事件上~注意是可以調用多次
  • app.toJSON:公有方法,將他作為json輸出時,會將subdomainOffset,proxy,env這幾個值,好像并沒有什么用..
  • app.use:很清楚,就是往middleware里面push而已,如果沒有打開試驗選項experimental,就只能傳入generator
  • app.callback:處理中間件并且進行返回的地方
  • app.createContext:私有方法,創建初始化的上下文
  • app.onerror:私有方法,默認的錯誤處理
  • respond:用來幫助返回的方法

主要的執行就是app.callback了,這里對代碼進行注釋

app.callback = function(){
  //如果打開了實驗的接口,就可以使用es7的async,不然使用koa-compose來進行包裝,然后再傳給co
  var fn = this.experimental
    ? compose_es7(this.middleware)
    : co.wrap(compose(this.middleware));
  var self = this;
  //看是否注冊了錯誤事件,沒有的話使用自身的onerror
  if (!this.listeners('error').length) this.on('error', this.onerror);
  return function(req, res){
    res.statusCode = 404;
    //在這里對上下文,以及req和res進行了包裝
    var ctx = self.createContext(req, res);
    //注冊一下這個是為了處理error
    //當error發生時,調用了context的onerror,里面emit了app的onerror,這樣上面的this.onerror就可以被觸發了。
    onFinished(res, ctx.onerror);
    //然后執行那個co包裝過的promise,成功的話執行respond方法,失敗了執行this.onerror方法
    fn.call(ctx).then(function () {
      respond.call(ctx);
    }).catch(ctx.onerror);
  }
};

respond方法也挺有意思,注釋一下


function respond() {
  if (false === this.respond) return;
  var res = this.res;
  //如果頭部已經發送,或者不能寫了,跳過
  if (res.headersSent || !this.writable) return;
  var body = this.body;
  var code = this.status;
  //如果是一些不需要返回體的code,比如204:沒有內容,比如304:未修改),直接返回
  if (statuses.empty[code]) {
    this.body = null;
    return res.end();
  }
  //如果是head請求,返回一個length
  //head請求允許請求某個資源的響應頭,而不要真正的資源本身
  if ('HEAD' == this.method) {
    if (isJSON(body)) this.length = Buffer.byteLength(JSON.stringify(body));
    return res.end();
  }
  //如果就是沒有內容,就設置一個默認的返回
  if (null == body) {
    this.type = 'text';
    body = this.message || String(code);
    this.length = Buffer.byteLength(body);
    return res.end(body);
  }
  //對不同的響應體進行判斷并且返回
  if (Buffer.isBuffer(body)) return res.end(body);
  if ('string' == typeof body) return res.end(body);
  if (body instanceof Stream) return body.pipe(res);
  body = JSON.stringify(body);
  this.length = Buffer.byteLength(body);
  res.end(body);
}

本文借鑒了

先寫到這里,順便給個github的傳送門,喜歡的朋友star一下啊,自己平時遇到的問題以及一下學習的經歷及所得都會在github上進行記錄~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容