nodejs之中間件框架Connect源碼淺談

前言

上篇文章介紹了express中間件的使用和如何本身實現中間件,也介紹了其實express中間件的實現依賴了Connect中間件框架,3.0版本以前的express框架的中間件徹底是由另外一個框架Connect實現的。雖然如今已經移除了對Connect的依賴,不過也是在express中實現了Connect。
能夠說是中間件讓express更加靈活,尤爲是其中的next的設計屬實巧妙,下面就經過源碼來簡單的分析下內部實現。node

開始

首先安裝connect
npm install connect
再看來一段官方的示例代碼express

var connect = require('connect');
var http = require('http');

var app = connect();

// gzip/deflate outgoing responses
var compression = require('compression');
app.use(compression());

// store session state in browser cookie
var cookieSession = require('cookie-session');
app.use(cookieSession({
    keys: ['secret1', 'secret2']
}));

// parse urlencoded request bodies into req.body
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended: false}));

// respond to all requests
app.use(function(req, res){
  res.end('Hello from Connect!\n');
});

//create node.js http server and listen on port
http.createServer(app).listen(3000);

咱們先來看定義了app變量,而後執行了connect方法,將方法的返回值給了app。先不急着看返回的是什麼,先來分析下。npm

代碼最後一行調用了http.createServer(app)。熟悉nodejs的人應該知道createServer接收一個函數,這個函數用來響應request事件,也就至關因而一個function(request,response)。因此咱們猜connect()返回的應該是一個函數,而且至少有requestresponse這兩個參數。數組

這時再來看源碼,首先看到源碼中只導出一個createServer
在這裏插入圖片描述
再找到這個createServer方法的定義
在這裏插入圖片描述
到這咱們就知道了在調用var app = connect()的時候,其實是調用源碼中的createServer()方法,方法內部建立一個函數對象,在對象上掛載了一些屬性,最後將這個函數對象返回。而且這裏app函數中是執行了app.handle方法,也就是說在http.createServer()中響應request事件的是app.handle方法。cookie

再總結一下上面的代碼,首先把app對象做爲參數傳給http.createServer(app).listen(3000)以後,返回了服務實例,而且監聽某一個端口,這時在全部請求的回調函數(request響應事件)中,都是執行了app.handle(req,res,next) session

到這就證實咱們上面的猜想是正確的。到這你可能會問那app.handle是哪來的呢?別急,繼續往下看。app

52行將proto對象上的屬性和方法複製到app中。
53行app對象也繼承了EventEmitter的原型。
54行route是表示請求路徑。
55行stack是用來存放全部中間件的數組。
(==注==:其中的merge是引入的utils-merge包的方法,功能就是將源對象的屬性合併到目標對象中。)框架

merge(app,proto);

這句就是把proto上的屬性方法合併到app對象上。proto對象又是什麼呢?
如今來解答上面的疑問,app.handle是哪來的,這個proto對象又是什麼?
源碼並不長,經過大體瀏覽能夠得知proto對象上有3個方法,分別是usehandlecall。這三個方法也是Connect框架實現的核心。咱們來逐一分析。
首先找到use方法,在使用中間件的時候都是經過app.use()函數
==proto.use==
在這裏插入圖片描述
81行——對參數進行了判斷,若是實際只傳入一個函數,保存這個函數並將路由設置爲"/"。函數

87~98行——處理了fn的幾種狀況:ui

  • typeof handle.handle === 'function'就至關於 fn === connect();
    意思就是傳進來的fn參數也是一箇中間件時,那麼將要存儲的handle爲這個子中間件的fn.handle()方法.
  • 當傳進來的參數fn是http.Server類的實例時,就把handle的值設置爲request事件的第一個監聽器。

101行——若是參數route參數以"/"結尾,則去掉"/"。

107行——能夠看到,將構形成特定格式的匿名對象放到stack數組中。stack就是一個裝中間件的容器數組。也就是在next中取出中間件的容器。

總結:proto.use方法就是用來添加中間件,也能夠理解爲將中間件進行登記,將構造好的特定格式的對象放到數組中。

==proto.handle==
handle方法是經過當前請求路徑找出stack數組中相匹配的中間件,並調用call方法。大可能是進行字符串的匹配,結合註釋能夠看懂一些細節操做。
在這裏插入圖片描述
135行——建立了next函數。
147行——從stack中取出當前項,並將index++。
150行——判斷是否還有須要處理的中間件,若是沒有則調用defer()。
160~180行——都是字符串的操做,對路由的匹配,若是不知足就執行next()跳到下一個,注意調用next的時候要將err傳遞進去。
183行——條件都知足則執行call函數,將stack數組的當前項的信息傳遞進去。
總體思路就是對router的匹配,若是匹配到了就經過call調用handle,若是沒有匹配就繼續執行下一個。這裏其實就是一個遞歸調用。

總結:handle是整個源碼中最長也是最核心的一個函數了。最主要的做用就是定義了next函數,經過遍歷取出當前項匿名對象的請求地址、中間件函數。而後經過當前的請求地址去匹配匿名對象的地址,若是匹配失敗則返回next(err);繼續處理stack中下一個中間件的信息。以此循環下去,直到stack中沒有值或者地址匹配成功,則調用call(layer.handle,route,err,req,res,next);。其中的layer.handle就是在use方法中構造的匿名對象中的中間件函數。由此咱們也能夠猜到call內部應該就是執行了這個中間件函數。

==call==
上面咱們猜想call中應該就是去執行匹配到的中間件函數。
在這裏插入圖片描述
call的實現仍是比較清晰的,基本能夠驗證咱們的猜想是正確的,call的主要任務就是執行handle方法中匹配到的中間件的函數。

當發生錯誤而且傳遞了4個參數時就會調用handle(err, req, res, next)函數,當沒有發生錯誤且傳遞的參數小於4個時就會調用handle(req, res, next),這裏的handle函數就是中間件函數。

在中間件邏輯代碼中,最後都會調用next函數,繼續匹配下一個中間件而後執行。

248行——當有錯誤而且傳遞的參數小於4個時,是否是走到兩個判斷中的,也就是說沒法執行中間件函數,而是直接調用next(error);。匹配下一個中間件,並將錯誤傳遞進去。如一直沒法知足條件,那麼就會走到handle中的defer(done, err);統一處理錯誤。

總結

Connect是一個很是簡短,可是設計很是精妙、代碼簡潔易讀的框架。以上對Connect的源碼進行了簡單的分析,最主要的就是proto上的三個方法,若是經過本文能對這三個方法有必定的瞭解,那麼你就掌握Connect框架實現的核心。

相關文章
相關標籤/搜索