express分析和對比

前言

目前express最新版本是4.16.2,因此本文分析也基於這個版本。目前從npm倉庫上來看express使用量挺高的,express月下載量約爲koa的40倍。因此目前研究下express仍是有必定意義的。vue

源碼分析

直接切入主題,因爲目前express是一個獨立的路由和中間件web框架。因此分析的方向也以這兩個爲主。源碼的研究只注重關鍵步驟和流程思想, 具體的hack,異常,邊界處理不作過多精力關注。java

關鍵步驟

中間件的執行

/**
 * //負責具體請求的邏輯處理,當一維layer中所對應的stack執行完後執行done(目的是爲了一維路由列表中進行下一個路由的匹配執行)。
 * @param done(路由中的next)
 */
Route.prototype.dispatch = function dispatch(req, res, done) {
    var idx = 0;
    var stack = this.stack;
    next();
    //遞歸方式執行stack中的layer,經過next控制流程的執行
    function next(err) {
        // 出錯直接退出當前stack和路由列表中回調的後續執行
        if (err && err === 'router') {
            return done(err)
        }
        //出錯直接退出當前stack列表後續執行,進行下一個路由匹配
        if (err && err === 'route') {
            return done();
        }
        var layer = stack[idx++];
        //執行結束
        if (!layer) {
            return done(err);
        }
        if (layer.method && layer.method !== method) {
            return next(err);
        }
            //調用具體註冊好的邏輯
        if (err) {
            layer.handle_error(err, req, res, next);
        } else {
            layer.handle_request(req, res, next);
        }
    }
};

說明 上面是源碼的部分摘要,去除了無關的信息,對關鍵步驟加了註解。node

路由匹配

/**
 * 對具體請求進行路由分發處理
 * out 是最後一個處理器,默認是請求的回調,不傳的話是內部提供的error handlers
 */
proto.handle = function handle(req, res, out) {
    var self = this;
    var idx = 0;
    var paramcalled = {};
    // middleware 和 roues
    var stack = self.stack;
    var done = restore(out, req, 'baseUrl', 'next', 'params');

    next();
    //遞歸方式遍歷註冊的一維路由
    function next(err) {
        var layerError = err === 'route'
            ? null
            : err;
        var layer,match,route;
        //取出註冊好的路由,進行請求匹配
        while (match !== true && idx < stack.length) {
            layer = stack[idx++];
            //req的path匹配,若是有註冊參數路由會解析參數到layer.params上
            match = matchLayer(layer, path);
            route = layer.route;
        }

        if (match !== true) {
            return done(layerError);
        }
        //將解析好的路徑參數放到請求對象上,以便後續參數回調邏輯的使用
        req.params = layer.params;

         //路徑參數回調回調  ,例如請求 /user/1 ,參數回調 app.param('user',cb1) app.get('/user/:user',cb2)   會先執行註冊的param cb1,而後纔會是router中註冊的cb2
        self.process_params(layer, paramcalled, req, res, function (err) {
            if (err) {
                return next(layerError || err);
            }
            if (route) {
                return layer.handle_request(req, res, next);
            }

        });
    }
};

說明 上面摘取了請求進行路由分發處理的關鍵步驟,並作了相應的註解。react

執行流程

當一個請求過來的時候交給handler方法,進行路由的匹配,以遞歸的方式遍歷(路由匹配一節中介紹過),當匹配到某一個路由的時候在dispatch執行,web應用啓動初始化前註冊好的回調邏輯,執行的方式也是以遞歸的方式(中間件執行一節中介紹過)。
請求匹配執行邏輯已經介紹過了,下面結合着初始化的邏輯,進行分析,具體以下圖所示。git

express-jsdt

說明github

初始化

針對上圖作一下說明,啓動服務應用的時候先進行初始化,註冊'/jsdt'的get請求,並給路由綁定相應的回調。其中咱們的路由放在一維的layer中,每一層路由,例如'/jsdt'又對應一個處理列表,這個二維列表裏又存儲着一系列layer(具體的回調處理邏輯),由於一維layer中已經記錄了路徑'/jsdt',因此二維的layer中就不用記錄路徑了,給個默認值'/',以保持一維layer和二維layer中結構的統一。web

請求

當一個請求過來的時候先去一維layer所存儲的路由中進行路徑匹配,以遞歸的方式。匹配到了,經過dispatch方法,在執行路由所對應的二維layer中的回調邏輯,也是以遞歸的方式執行,在遞歸的過程當中,若是發生異常,經過路由中傳遞過來的out,直接進行下一個路由的匹配。vue-router

/**
 *遞歸執行stack中的handle
 * @param out  路由中的next
 */
Route.prototype.dispatch = function (req, res, out) (xxx)

當執行遇到res.end()的時候,數據響應完後,整個請求響應過程就結束了。express

koa vs express

kao源碼去年的時候有分析過,如今對比分析思考下。
koa比較迷你,微內核,拓展性強,因此一些web框架例如阿里的eggjs就是基於koa。而express集成了路由和static中間件因此顯得重一些。

express中間件和koa中間件區別,以及與redux中間件區別 ?

網上不少文章都說一個是線性的,一個是洋蔥模型。由於兩個我都研究過,我覺的這種說法不對,其實執行的時候都是洋蔥形。主要的區別是koa內核底層原生支持async語法,koa中的middlewares通過compose之後,每次執行到await next返回的都是一個promise,因此咱們能夠在頂層加一個try……catch進行異常捕獲,這算是koa比較方便的一點。還有一點不少人提到過說koa能夠記錄處理時間,那是由於每次res.body賦值的時候並無res.end,因此在第一個中間件很容易記錄處理的時間,以下所示,在中間件執行完後,在handleResponse中才會將請求處理結果返回給客戶端。npm

fn(ctx)[是一個當即狀態的promise].then(handleResponse).catch(onerror);

而express每次res.send的時候數據已經發給客戶端了,固然也能夠實現這種需求,只不過沒有koa方便。多說幾句,其實java中也有相似的實現,例如java中的aop,過濾器,將通用的邏輯,例如日誌,權限等模塊經過配置的方式進行靈活的插入,配置在xml中。比較靈活,每一個模塊之間互相解耦,根據需求實現可插拔效果。
在說一下與react中redux中間件機制區別,redux中的applyMiddleware中間件機制,能夠在處理store先後加一些通用處理,利用高階函數compose,經過reduce將多個函數組合成一個可執行執行函數

//applyMiddleware.js
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
//compose.js
export default function compose(...funcs) {
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

組合好最後執行的時候是洋蔥模型效果,舉個例子 compose(a, b, c)變成 a(b(c())),而a,b,c的結構相似下面這種形式

function fnA(next) {
  return function() {
    console.log('fnA start')
    next()
    console.log('fnA end')
  }
}

因此a(b(c()))()執行的時候,就會經過next來控制調用下一個中間件,總體的執行等價於express遞歸調用方式

express路由和 vue路由共同點

vue中也有路由,官方的vue-router,他解決的是url和模板組件匹配渲染的問題, 而express中解決的url和handler匹配執行的問題,而koa內核裏面沒有集成路由。 從vue-router和express路由中能夠看出路由的共性,是爲了解決路徑和相應處理邏輯的匹配問題在web開發中。

toy版本express

根據上述分析的邏輯,實現了一個簡化版的express ,融入了express的核心思想,有詳盡的步驟註釋,有須要的能夠參考下。

參考源碼版本說明
express 4.16.2
koa 2.2
redux 3.7.2
參考連接
https://github.com/koajs/koa/...
https://www.reddit.com/r/node...
https://blog.jscrambler.com/m...

相關文章
相關標籤/搜索