先複習一下使用原生 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()
傳入的中介軟體 “捏” 成的。也就是說,中介軟體處於核心位置,根據我們想要的邏輯處理請求和響應。