本文來自《心譚博客·深刻koa源碼:架構設計》javascript
前端面試、設計模式手冊、Webpack4教程、NodeJs實戰等更多專題,請來導航頁領取食用前端
全部系列文章都放在了Github。歡迎交流和Star ✿✿ ヽ(°▽°)ノ ✿java
最近讀了 koa 的源碼,理清楚了架構設計與用到的第三方庫。本系列將分爲 3 篇,分別介紹 koa 的架構設計和 3 個核心庫的原理,最終會手動實現一個簡易的 koa。git
koa 的實現都在倉庫的lib
目錄下,以下圖所示,只有 4 個文件:github
對於這四個文件,根據用途和封裝邏輯,能夠分爲 3 類:req 和 res,上下文以及 application。面試
對應的文件是:request.js
和 response.js
。分別表明着客戶端請求信息和服務端返回信息。設計模式
這兩個文件在實現邏輯上徹底一致。對外暴露都是一個對象,對象上的屬性都使用了getter
或setter
來實現讀寫控制。數組
對應的文件是:context.js
。存了運行環境的上下文信息,例如cookies
。服務器
除此以外,由於request
和response
都屬於上下文信息,因此經過delegate.js
庫來實現了對request.js
和response.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.js
。這個文件的邏輯是最重要的,它的做用主要是:
使用 koa 時候,咱們常經過listen
或者callback
來啓動服務器:
const app = new Koa();
app.listen(3000); // listen啓動
http.createServer(app.callback()).listen(3000); // callback啓動
複製代碼
這兩種啓動方法是徹底等價的。由於listen
方法內部,就調用了callback
,而且將它傳給http.createServer
。接着看一下callback
這個方法主要作了什麼:
koa-compose
將中間件串聯起來(下文再講)。http.createServer()
的函數,而且返回。http.createServer
傳給函數參數的請求信息和返回信息,都被這個函數拿到了。而且傳給createContext
方法,生成本次請求的上下文。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.headers
是 ctx.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 中處理中間件的函數是use
和handleRequest
:
use
函數:傳入async/await
函數,並將其放入 application 實例上的middleware
數組中。若是傳入是 generator,會調用koa-conver
庫將其轉化爲async/await
函數。handleRequest(ctx, fnMiddleware)
函數:傳入的fnMiddleware
是已經串聯好的中間件,函數所作的工做就是再其後再添加一個返回給客戶端的函數和錯誤處理函數。返回給客戶端的函數其實就是respond
函數,裏面經過調用res.end()
來向客戶端返回信息,整個流程就走完了。