Koa原始碼閱讀(一)從搭建Web伺服器說起

Hoxz發表於2018-12-06

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

相關文章