Koa原始碼閱讀(一)從搭建Web伺服器說起
先複習一下使用原生 Node.js 搭建一個 Web 伺服器。
var http = require('http'); var server = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}) res.end('Hello world\n') }) server.listen(3000) 複製程式碼
可以看到,我們只需要關注http.createServer()
傳入的回撥函式和server.listen()
傳入的引數即可。一般來講,server.listen()
傳入 Web 伺服器監聽的埠號,而http.createServer()
傳入的回撥函式則負責處理 HTTP 請求並給出響應。
相同的邏輯對應到 Koa 上來,程式碼量差不多。
const koa = require('koa'); const app = new koa(); app.use(ctx => { ctx.body = 'Hello world' }) app.listen(3000) 複製程式碼
仔細觀察我們發現,server.listen
對應於app.listen
,而http.createServer()
傳入的回撥函式在 Koa 裡則是利用app.use()
傳入的。實際上,處理請求和響應的操作就是由app.use()
傳入的函式完成的。
基於這個思路,我們可以開始分析 Koa 原始碼中涉及到上面描述的部分。
原始碼檔案
Koa 的原始碼只有四個檔案。其中,負責對外暴露方法的是application.js
,context.js
封裝了請求和響應作為上下文ctx
,而request.js
(請求)和response.js
(響應)則為context.js
提供支援。
核心檔案是application.js
,主要是兩個方法:
1. app.listen() - 監聽埠
封裝並不複雜,僅僅是將原生 Node.js 啟動 Web 伺服器的操作放在了一個函式裡。
listen(...args) { const server = http.createServer(this.callback()); return server.listen(...args); } 複製程式碼
看到這裡大概也能猜出來,我們的邏輯(處理請求和響應)都在this.callback()
裡面。這也是後面要講的重頭戲。
2. app.use() - 新增中介軟體
除去校驗引數合法性外,真正實現功能的只有一句:
use(fn) { // ... this.middleware.push(fn); // ... } 複製程式碼
實際上就是將傳入的中介軟體函式新增到this.middleware
中。最終,就是這些中介軟體函式,構成了處理請求和響應的絕大多數邏輯。
誰來處理中介軟體
檔案開始的時候,我們已經得到一個思路,http.createServer()
傳入的回撥函式負責處理每個 HTTP 請求並給出響應,而現在我們發現傳入的是this.callback()
的返回值,我們來看看它的程式碼。
callback() { const fn = compose(this.middleware); // ... const handleRequest = (req, res) => { const ctx = this.createContext(req, res); return this.handleRequest(ctx, fn); }; return handleRequest; } 複製程式碼
返回的handleRequest
區域性變數就是我們一直提到的那個回撥函式,它與原生 Node.js 搭建的伺服器一樣,接收請求(req)和響應(res)兩個引數。每次請求到來時,這個函式都會被呼叫,它完成兩個工作:
-
建立一個上下文
ctx
,封裝了本次的請求和響應 -
將上下文
ctx
和函式fn
交由this.handleRequest()
處理
對了,這個函式的第一行我們沒有介紹,它用到了app.use()
傳進來的中介軟體this.middleware
。
const fn = compose(this.middleware); 複製程式碼
中介軟體機制是 Koa 設計中非常巧妙的一部分,利用中介軟體我們可以為 Web 伺服器提供各種各樣的功能。鑑於篇幅,我們只介紹如何把傳入的多箇中間件變成我們想要的回撥函式。
這裡用到的是koa-compose
這個 NPM 包,它把傳入的多箇中間件 "捏" 成一個回撥函式fn
,由它對上下文ctx
進行處理,當然也就是 HTTP 請求和響應。
處理請求和響應
上節提到,上下文ctx
和函式fn
交給了this.handleRequest()
處理,它進行了以下幾項工作:
-
在
ctx
中將響應預設置為404 -
定義錯誤處理函式
onerror
,具體會由ctx.onerror()
執行 -
定義響應處理函式
handleResponse
,具體會由this.respond()
執行 -
呼叫中介軟體 “捏” 成的單個回撥函式
fn
處理上下文ctx
,其返回一個 Promise 物件,在其then中發出響應(呼叫handleResponse
),若出錯則處理錯誤(呼叫onerror
)
總結
總的來說,可以將由 Koa 搭建的 Web 伺服器的工作原理分為兩個過程:
1. 啟動伺服器
利用this.callback()
將中介軟體 “捏” 成一個回撥函式傳給http.createServer
,同時例項化了一個Server
物件,呼叫其listen
方法啟動伺服器。
2. 處理請求並響應
this.callback()
返回的是一個回撥函式,每個新的請求到來,Server
就會呼叫它並傳入請求和響應兩個引數。它會建立包含 req 和 res 的上下文ctx
,並呼叫回撥函式fn
處理ctx
,繼而發出響應或錯誤。而fn
是由我們呼叫app.use()
傳入的中介軟體 “捏” 成的。也就是說,中介軟體處於核心位置,根據我們想要的邏輯處理請求和響應。