深刻koa源碼(一):架構設計

本文來自《心譚博客·深刻koa源碼:架構設計》javascript

前端面試、設計模式手冊、Webpack4教程、NodeJs實戰等更多專題,請來導航頁領取食用前端

全部系列文章都放在了Github。歡迎交流和Star ✿✿ ヽ(°▽°)ノ ✿java

最近讀了 koa 的源碼,理清楚了架構設計與用到的第三方庫。本系列將分爲 3 篇,分別介紹 koa 的架構設計和 3 個核心庫的原理,最終會手動實現一個簡易的 koagit

koa 的實現都在倉庫的lib目錄下,以下圖所示,只有 4 個文件:github

對於這四個文件,根據用途和封裝邏輯,能夠分爲 3 類:req 和 res,上下文以及 application。面試

req 和 res

對應的文件是:request.jsresponse.js。分別表明着客戶端請求信息和服務端返回信息。設計模式

這兩個文件在實現邏輯上徹底一致。對外暴露都是一個對象,對象上的屬性都使用了gettersetter來實現讀寫控制。數組

上下文

對應的文件是:context.js。存了運行環境的上下文信息,例如cookies服務器

除此以外,由於requestresponse都屬於上下文信息,因此經過delegate.js庫來實現了對request.jsresponse.js上全部屬性的代理。例如如下代碼:cookie

/** * Response delegation. */
delegate(proto, "response")
  .method("attachment")
  .method("redirect");

/** * Request delegation. */

delegate(proto, "request")
  .method("acceptsLanguages")
  .method("acceptsEncodings");
複製代碼

使用代理的另一個好處就是:更方便的訪問 req 和 res 上的屬性。好比在開發 koa 應用的時候,能夠經過ctx.headers來讀取客戶端請求的頭部信息,不須要寫成ctx.res.headers了(這樣寫沒錯)。

注意:req 和 res 並非在context.js中被綁定到上下文的,而是在application被綁定到上下文變量ctx中的。緣由是由於每一個請求的 req/res 都不是相同的。

Application

對應的文件是: application.js。這個文件的邏輯是最重要的,它的做用主要是:

  • 給用戶暴露服務啓動接口
  • 針對每一個請求,生成新的上下文
  • 處理中間件,將其串聯

對外暴露接口

使用 koa 時候,咱們常經過listen或者callback來啓動服務器:

const app = new Koa();
app.listen(3000); // listen啓動
http.createServer(app.callback()).listen(3000); // callback啓動
複製代碼

這兩種啓動方法是徹底等價的。由於listen方法內部,就調用了callback,而且將它傳給http.createServer。接着看一下callback這個方法主要作了什麼:

  1. 調用koa-compose將中間件串聯起來(下文再講)。
  2. 生成傳給http.createServer()的函數,而且返回。
  • http.createServer傳給函數參數的請求信息和返回信息,都被這個函數拿到了。而且傳給createContext方法,生成本次請求的上下文。
  • 將生成的上下文傳給第 1 步生成的中間件調用鏈,這就是爲何咱們在中間件處理邏輯的時候可以訪問ctx

生成新的上下文

這裏上下文的方法對應的是createContext方法。這裏我以爲更像語法糖,是爲了讓 koa 使用者使用更方便。好比如下這段代碼:

// this.request 是 request.js 暴露出來的對象,將其引用保存在context.request中
// 用戶能夠直接經過 ctx.屬性名 來訪問對應屬性
const request = (context.request = Object.create(this.request));

// 這個req是本次請求信息,是由 http.createServer 傳遞給回調函數的
context.req = request.req = response.req = req;
複製代碼

讀到這裏,雖然能夠解釋 ctx.headersctx.request.headers 的語法糖這類問題。可是感受怪怪的。就以這個例子,ctx.headers 訪問的是 ctx.reqeust 上的 headers,而不是本次請求信息上的headers。本次請求信息掛在了ctx.req上。

讓咱們再回到reqeust.js的源碼,看到了headers的 getter 實現:

get headers() {
  return this.req.headers;
}
複製代碼

ok,看來這裏的this就是指的上下文環境咯。那麼確定是在application.js中某個地方改變了this的指向。果真,在application.js的構造函數中能夠看到:

this.request = Object.create(request);
複製代碼

application 實例上的 request 被傳遞給了 context.request,此時 this 天然指向了 context。

能夠看到,koa 爲了讓開發者使用方便,在上下文上作了不少工做。

中間件機制

中間件的設計是 koa 最重要的部分,實現上用到了koa-compose庫來串聯中間件,造成「洋蔥模型」。關於這個庫,放在第二篇關於 koa 核心庫的介紹中說明。

application 中處理中間件的函數是usehandleRequest

  • use函數:傳入async/await函數,並將其放入 application 實例上的middleware數組中。若是傳入是 generator,會調用koa-conver庫將其轉化爲async/await函數。
  • handleRequest(ctx, fnMiddleware)函數:傳入的fnMiddleware是已經串聯好的中間件,函數所作的工做就是再其後再添加一個返回給客戶端的函數和錯誤處理函數。返回給客戶端的函數其實就是respond函數,裏面經過調用res.end()來向客戶端返回信息,整個流程就走完了。
相關文章
相關標籤/搜索