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...

相关文章
相关标签/搜索