上篇文章介紹了express中間件的使用和如何本身實現中間件,也介紹了其實express中間件的實現依賴了Connect中間件框架,3.0版本以前的express框架的中間件徹底是由另外一個框架Connect實現的。雖然如今已經移除了對Connect的依賴,不過也是在express中實現了Connect。
能夠說是中間件讓express更加靈活,尤爲是其中的next的設計屬實巧妙,下面就經過源碼來簡單的分析下內部實現。node
首先安裝connectnpm 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()
返回的應該是一個函數,而且至少有request
和response
這兩個參數。數組
這時再來看源碼,首先看到源碼中只導出一個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個方法,分別是use
、handle
、call
。這三個方法也是Connect框架實現的核心。咱們來逐一分析。
首先找到use方法,在使用中間件的時候都是經過app.use()
函數
==proto.use==
81行——對參數進行了判斷,若是實際只傳入一個函數,保存這個函數並將路由設置爲"/"。函數
87~98行——處理了fn的幾種狀況:ui
typeof handle.handle === 'function'
就至關於 fn === connect();
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框架實現的核心。