• express与koa对比


    使用体验
    koa
    const Koa = require('koa');
    const app = new Koa();
    app.use(ctx => {
    ctx.body = 'Hello Koa';
    });
    app.listen(3000);
    1
    2
    3
    4
    5
    6
    express
    const app = require("express")();
    app.use((req,res,next)=>{
    res.status(200).send("<h1>headers ...</h1>");
    });
    app.listen(3001);
    1
    2
    3
    4
    5
    注意:本文全部采用es6语法编写,如果环境不支持请自行升级node或者使用babel进行转码。

    启动方式
    koa采用了new Koa()的方式,而express采用传统的函数形式,对比源码如下:
    //koa
    const Emitter = require('events');
    module.exports = class Application extends Emitter {
    ...
    }
    //express
    exports = module.exports = createApplication;
    function createApplication() {
    ...
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    可以看到koa@2采用了es6的语法实现,继承了Emitter类,具体信息可以参考Emitter说明。这就意味着koa@2只能在es6以上的环境下运行,低版本可以考虑使用koa@1.x。而express则比较传统,使用的是function的形式导出。
    2. 中间件形式二者不一样,这是由二者处理中间件的逻辑差异导致的,实际上这也是二者最根本的差别,具体的分析留作后面进行对比,这里主要对比二者的使用上的差别,如下所示:

    express处理多个中间件
    const app = require("express")();
    app.use((req,res,next)=>{
    console.log("first");
    //next();
    });
    app.use((req,res,next)=>{
    console.log("second");
    //next();
    });
    app.use((req,res,next)=>{
    console.log("third");
    res.status(200).send("<h1>headers ...</h1>");
    });
    app.listen(3001);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    如果写成这样,终端只会打印出first,而且不会反悔,前端请求会一直等待到超时,导致这一问题的原因是:express必须主动调用next()才能让中间价继续执行,放开注释即可。这也保证了我们可以自主控制如何响应请求。

    koa处理多个中间件
    const Koa = require('koa');
    const app = new Koa();
    app.use((ctx,next) => {
    ctx.body = 'Hello Koa-1';
    next();
    });
    app.use((ctx,next) => {
    ctx.body = 'Hello Koa-2';
    next();
    });
    app.use((ctx,next) => {
    ctx.body = 'Hello Koa-3';
    next();
    });
    app.listen(3000);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    与express类似,koa中间件的入参也有两个,后一个就是next。next的功能与express一样,这里不再赘述。

    上面介绍了koa的next()的功能,这里的next()需要同步调用,千万不要采用异步调用,不要写成下面的形式,这样相当于未调用next(),具体原因后面源码部分会分析:

    app.use((ctx,next) => {
    ctx.body = 'Hello Koa-2';
    setTimeout(()=>next(),3000);
    //next();
    });
    1
    2
    3
    4
    5
    虽然上面分析了二者的使用逻辑不一样,但是由于koa在入参处给出了context,而该结构体包含了我们返回请求的所有信息,所以我们仍然可以写出下面的代码:

    const Koa = require('koa');
    const app = new Koa();

    app.use((ctx)=>{
    const res = ctx.res;
    res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8','Accept-Language':'zh-CN,zh;q=0.8,en;q=0.6'});
    res.end('<h1>标题</h1>');
    });

    // response
    app.use(ctx => {
    ctx.body = 'Hello Koa';
    });
    app.listen(3000);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    这样的逻辑就和express很类似了,原理也一样。这样写以后,前端的请求得到的结果就是<h1>标题</h1>,而后续的app.use实际并没有得到执行。

    express分路由处理
    express的代码一般如下:

    const app = require("express")();
    app.use("/first",(req,res,next)=>{
    console.log("first");
    res.status(200).send("<h1>headers-first ...</h1>");
    });
    app.use("/second",(req,res,next)=>{
    console.log("second");
    res.status(200).send("<h1>headers-second ...</h1>");
    });
    app.use("/third",(req,res,next)=>{
    console.log("third");
    res.status(200).send("<h1>headers-third ...</h1>");
    });
    app.listen(3001);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    这很好理解,根据请求路径返回不同结果,koa呢?

    koa分路由处理
    const Koa = require('koa');
    const app = new Koa();
    app.use("/",ctx => {
    ctx.body = 'Hello Koa';
    });
    app.listen(3000);
    1
    2
    3
    4
    5
    6
    这么写会报错,因为koa本身并不支持按路由相应,如果需要这么做,可以通过引入第三方包实现。在koajs中有一个简单的router包。
    具体写法如下:

    //摘抄自Koa Trie Router
    const Koa = require('koa')
    const Router = require('koa-trie-router')

    let app = new Koa()
    let router = new Router()

    router
    .use(function(ctx, next) {
    console.log('* requests')
    next()
    })
    .get(function(ctx, next) {
    console.log('GET requests')
    next()
    })
    .put('/foo', function (ctx) {
    ctx.body = 'PUT /foo requests'
    })
    .post('/bar', function (ctx) {
    ctx.body = 'POST /bar requests'
    })

    app.use(router.middleware())
    app.listen(3000)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    在具体应用中也可以采用其他的路由包来做,在github上能搜到不少。
    另外,由于实现的原因,下面介绍一个有意思的现象,看下面两段代码,初衷是打印请求处理耗时。

    koa版本
    const Koa = require('koa');
    const app = new Koa();
    app.use((ctx,next) => {
    ctx.body = 'Hello Koa-1';
    let start = new Date();
    next().then(()=>{
    console.log("time cost:",new Date()-start);
    });
    });
    app.use((ctx,next) => {
    ctx.body = 'Hello Koa-2';
    next();
    });
    app.use((ctx,next) => {
    ctx.body = 'Hello Koa-3';
    next();
    });
    app.listen(3000);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    由于koa采用了promise的方式处理中间件,next()实际上返回的是一个promise对象,所以可以用上面简单的方式记录处理耗时。如果在es7下,可以采用更简单的写法:

    const Koa = require('koa');
    const app = new Koa();
    app.use(async (ctx,next) => {
    ctx.body = 'Hello Koa-1';
    let start = new Date();
    await next();
    console.log("time cost:",new Date()-start);
    });
    app.use(async (ctx,next) => {
    ctx.body = 'Hello Koa-2';
    //这里用了一个定时器表示实际的操作耗时
    await new Promise((resolve,reject)=>setTimeout(()=>{next();resolve();},3000));
    });
    app.use((ctx,next) => {
    ctx.body = 'Hello Koa-3';
    next();
    });
    app.listen(3000);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    这样只需要在入口放置一个中间件即可完成耗时记录。

    express版本
    由于express并没有使用promise而是采用了回调的方式处理中间件,所以无法采用上面这样便利的方式获取耗时。即便是对next()进行封装,也无济于事,因为必须保证后续的next()全部都被封装才能得到正确的结果。
    下面给出一个参考实现:

    let time = null;
    .use('/', (req, res, next) => {
    time = Date.now();
    next()
    })
    .use('/eg', bidRequest)
    .use('/', (req, res, next) => {
    console.log(`<= time cost[${req.baseUrl}] : `, Date.now() - time, 'ms');
    })
    1
    2
    3
    4
    5
    6
    7
    8
    9
    总结
    koa和express的区别还是比较大的,koa的内容很少,就是对nodejs本身的createServer函数做了简单的封装,没有做很多的延伸;而express主要是比koa多了router。二者的的代码思路还是很不一样的,不过实际使用中并不会有太大障碍。

    源码分析
    koa
    koa的源码主要有四个文件:application.js, context.js, request.js, response.js

    context.js
    context没有实际功能性代码,只是一些基础函数和变量,下面是代码片段。

    inspect() {
    return this.toJSON();
    },
    toJSON() {
    return {
    request: this.request.toJSON(),
    response: this.response.toJSON(),
    app: this.app.toJSON(),
    originalUrl: this.originalUrl,
    req: '<original node req>',
    res: '<original node res>',
    socket: '<original node socket>'
    };
    },
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    request.js
    该文件中主要是一堆set和get函数,主要是用于获取请求结构体的特定字段或者修改特定字段,比如下面获取ip的函数,代码很好理解:

    get ips() {
    const proxy = this.app.proxy;
    const val = this.get('X-Forwarded-For');
    return proxy && val
    ? val.split(/s*,s*/)
    : [];
    },
    1
    2
    3
    4
    5
    6
    7
    response.js
    response与request对应,主要是一些处理res的工具类,下面是代码片段,用于设置和获取res的content-length:

    set length(n) {
    this.set('Content-Length', n);
    },
    get length() {
    const len = this.header['content-length'];
    const body = this.body;

    if (null == len) {
    if (!body) return;
    if ('string' == typeof body) return Buffer.byteLength(body);
    if (Buffer.isBuffer(body)) return body.length;
    if (isJSON(body)) return Buffer.byteLength(JSON.stringify(body));
    return;
    }

    return ~~len;
    },
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    其中用到了~~len,这个有点意思,两次取反,可以保证输出为数字,如果len为字符串则返回0。(第一次见…)

    application.js
    上文中用到的app就是在该文件中定义的,也是koa的核心所在,这里挑选几个成员函数进行分析(整个文件代码也就不到250行,自己看完压力也不大)。

    module.exports = class Application extends Emitter {
    /*
    构造函数:把req,res,env等常用的变量全都塞进了context,所以我们在中间件中拿到context以后,就可以随心所欲地操作req和res了。
    */
    constructor() {
    super();
    this.proxy = false;
    this.middleware = [];
    this.subdomainOffset = 2;
    this.env = process.env.NODE_ENV || 'development';
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
    }
    /*
    实际就是调用了nodejs本身的createServer,没有任何区别。
    */
    listen() {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen.apply(server, arguments);
    }
    //下面分析
    use(fn) {
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
    if (isGeneratorFunction(fn)) {
    deprecate('Support for generators will be removed in v3. ' +
    'See the documentation for examples of how to convert old middleware ' +
    'https://github.com/koajs/koa/blob/master/docs/migration.md');
    fn = convert(fn);
    }
    debug('use %s', fn._name || fn.name || '-');
    this.middleware.push(fn);
    return this;
    }
    //下面分析
    callback() {
    const fn = compose(this.middleware);

    if (!this.listeners('error').length) this.on('error', this.onerror);

    const handleRequest = (req, res) => {
    res.statusCode = 404;
    const ctx = this.createContext(req, res);
    const onerror = err => ctx.onerror(err);
    const handleResponse = () => respond(ctx);
    onFinished(res, onerror);
    return fn(ctx).then(handleResponse).catch(onerror);
    };

    return handleRequest;
    }

    //新建context
    createContext(req, res) {
    const context = Object.create(this.context);
    const request = context.request = Object.create(this.request);
    const response = context.response = Object.create(this.response);
    context.app = request.app = response.app = this;
    context.req = request.req = response.req = req;
    context.res = request.res = response.res = res;
    request.ctx = response.ctx = context;
    request.response = response;
    response.request = request;
    context.originalUrl = request.originalUrl = req.url;
    context.cookies = new Cookies(req, res, {
    keys: this.keys,
    secure: request.secure
    });
    request.ip = request.ips[0] || req.socket.remoteAddress || '';
    context.accept = request.accept = accepts(req);
    context.state = {};
    return context;
    }
    //下面分析
    function respond(ctx) {
    // allow bypassing koa
    if (false === ctx.respond) return;

    const res = ctx.res;
    if (!ctx.writable) return;

    let body = ctx.body;
    const code = ctx.status;

    // ignore body
    if (statuses.empty[code]) {
    // strip headers
    ctx.body = null;
    return res.end();
    }

    if ('HEAD' == ctx.method) {
    if (!res.headersSent && isJSON(body)) {
    ctx.length = Buffer.byteLength(JSON.stringify(body));
    }
    return res.end();
    }

    // status body
    if (null == body) {
    body = ctx.message || String(code);
    if (!res.headersSent) {
    ctx.type = 'text';
    ctx.length = Buffer.byteLength(body);
    }
    return res.end(body);
    }

    // responses
    if (Buffer.isBuffer(body)) return res.end(body);
    if ('string' == typeof body) return res.end(body);
    if (body instanceof Stream) return body.pipe(res);

    // body: json
    body = JSON.stringify(body);
    if (!res.headersSent) {
    ctx.length = Buffer.byteLength(body);
    }
    res.end(body);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    下面重点看use,callback,respond这三个函数,实际上理解koa的信息流看这三个函数的源码就差不多足够了。
    use : 内容不多,其中第一个if用于安全检查,第二个if用于实现对generator函数的兼容,具体实现过程在is-generator-function这个包里面,有兴趣可以看看,还是挺有技巧的,参考借用。use最终仅仅就是把中间件push到了this.middleware数组里,并没有任何实质的逻辑操作。
    respond : 该函数就是响应请求的地方,这也是为什么我们可以不用主动地响应请求。函数里做了很多判断,主要是防止二次响应以及特殊特定的响应的请求。
    callback : callback用于生成createServer函数的回调,即handleRequest函数。handleRequest的返回值正是一个promise对象。注意这里调用了一个compose方法,该方法的作用就是把中间件数组转换成一个函数,以方便使用。具体的实现在koa-compose这个包里,这里摘抄其中的一段来分析。

    //这就是compose(...)返回的函数
    function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
    if (i <= index) return Promise.reject(new Error('next() called multiple times'))
    index = i
    let fn = middleware[i]
    if (i === middleware.length) fn = next
    if (!fn) return Promise.resolve()
    try {
    return Promise.resolve(fn(context, function next () {
    return dispatch(i + 1)
    }))
    } catch (err) {
    return Promise.reject(err)
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    可以看到,实际上这里就是用闭包实现了对中间件数组的遍历。具体思路会把第i+1个中间件作为next传给第i个中间件,这也就是为什么必须主动调用next的原因,以为如果不主动调用next这一循环就会提前结束了,后续的中间件就无法得到执行。

    到此为止,koa源码分析就结束了,koa源码很少,没有多余的东西,甚至连路由都需要引入其他的包。

    express
    express的源码比koa多了不少东西,这里仅仅对比核心部分,忽略其他部分的内容。

    //express.js
    function createApplication() {
    var app = function(req, res, next) {
    app.handle(req, res, next);
    };

    mixin(app, EventEmitter.prototype, false);
    mixin(app, proto, false);

    // expose the prototype that will get set on requests
    app.request = Object.create(req, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
    })

    // expose the prototype that will get set on responses
    app.response = Object.create(res, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
    })

    app.init();
    return app;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    这里的express就是我们上文中引入的express对象,可以看到,实际上该函数就是把一些常用的功能和变量绑定到了app对象中去,我们在代码中使用的app.eg_funcs之类的方法都是从这里继承得到的。实际上该对象并不局限于使用app.listen()来启动一个服务,下面是listen函数的代码。

    //application.js
    app.listen = function listen() {
    var server = http.createServer(this);
    return server.listen.apply(server, arguments);
    };
    1
    2
    3
    4
    5
    调用app.listen可以启动一个服务器,实际上我们也可以直接手动写出这两句代码来启动一个服务。在socket.io和https服务中就需要自己来完成这一过程。
    下面是app.use的源码:

    app.use = function use(fn) {
    var offset = 0;
    var path = '/';

    // default path to '/'
    // disambiguate app.use([fn])
    if (typeof fn !== 'function') {
    var arg = fn;

    while (Array.isArray(arg) && arg.length !== 0) {
    arg = arg[0];
    }

    // first arg is the path
    if (typeof arg !== 'function') {
    offset = 1;
    path = fn;
    }
    }

    var fns = flatten(slice.call(arguments, offset));

    if (fns.length === 0) {
    throw new TypeError('app.use() requires middleware functions');
    }

    // setup router
    this.lazyrouter();
    var router = this._router;

    fns.forEach(function (fn) {
    // non-express app
    if (!fn || !fn.handle || !fn.set) {
    return router.use(path, fn);
    }

    debug('.use app under %s', path);
    fn.mountpath = path;
    fn.parent = this;

    // restore .app property on req and res
    router.use(path, function mounted_app(req, res, next) {
    var orig = req.app;
    fn.handle(req, res, function (err) {
    setPrototypeOf(req, orig.request)
    setPrototypeOf(res, orig.response)
    next(err);
    });
    });

    // mounted an app
    fn.emit('mount', this);
    }, this);

    return this;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    这里有一些对参数判断的逻辑,比如第一个参数如果是路径还是函数,不过平时很少这么写。

    从中可以看到实际上express是调用了router的use方法对中间件进行处理。router.use定义在/router/index.js中, 源码如下:

    proto.use = function use(fn) {
    var offset = 0;
    var path = '/';

    // default path to '/'
    // disambiguate router.use([fn])
    if (typeof fn !== 'function') {
    var arg = fn;

    while (Array.isArray(arg) && arg.length !== 0) {
    arg = arg[0];
    }

    // first arg is the path
    if (typeof arg !== 'function') {
    offset = 1;
    path = fn;
    }
    }

    var callbacks = flatten(slice.call(arguments, offset));

    if (callbacks.length === 0) {
    throw new TypeError('Router.use() requires middleware functions');
    }

    for (var i = 0; i < callbacks.length; i++) {
    var fn = callbacks[i];

    if (typeof fn !== 'function') {
    throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn));
    }

    // add the middleware
    debug('use %o %s', path, fn.name || '<anonymous>')

    var layer = new Layer(path, {
    sensitive: this.caseSensitive,
    strict: false,
    end: false
    }, fn);

    layer.route = undefined;

    this.stack.push(layer);
    }

    return this;
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    其中前大半段主要是一些准备工作(这种写法在express貌似很常见)。后面看到与koa直接把中间件push到数组的做法不同的是,express会把中间件封装成一个Layer,这样做也是为了更好地控制中间件的执行。Layer的代码在/router/layer.js中。(这里不再分析)

    下面开始分析express是怎么响应请求的,从上面listen部分的代码可以看到,我们给createServer传了一个this,而这个this正是express()的返回值,定义在application.js里,源码如下:

    var app = function(req, res, next) {
    app.handle(req, res, next);
    };
    1
    2
    3
    可以看到实际上app是调用了handle方法,而该方法是从application对象继承过来的,而查看application.js发现了下面代码:

    //初始化 this._router
    this._router = new Router({
    caseSensitive: this.enabled('case sensitive routing'),
    strict: this.enabled('strict routing')
    });

    this._router.use(query(this.get('query parser fn')));
    this._router.use(middleware.init(this));
    //使用 this._router
    app.handle = function handle(req, res, callback) {
    var router = this._router;

    // final handler
    var done = callback || finalhandler(req, res, {
    env: this.get('env'),
    onerror: logerror.bind(this)
    });

    // no routes
    if (!router) {
    debug('no routes defined on app');
    done();
    return;
    }

    router.handle(req, res, done);
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    可以看到实际上这里调用的是router.handle,下面看router的源码:

    proto.handle = function handle(req, res, out) {
    var self = this;

    debug('dispatching %s %s', req.method, req.url);

    var idx = 0;
    var protohost = getProtohost(req.url) || ''
    var removed = '';
    var slashAdded = false;
    var paramcalled = {};

    // store options for OPTIONS request
    // only used if OPTIONS request
    var options = [];

    // middleware and routes
    var stack = self.stack;

    // manage inter-router variables
    var parentParams = req.params;
    var parentUrl = req.baseUrl || '';
    var done = restore(out, req, 'baseUrl', 'next', 'params');

    // setup next layer
    req.next = next;

    // for options requests, respond with a default if nothing else responds
    if (req.method === 'OPTIONS') {
    done = wrap(done, function(old, err) {
    if (err || options.length === 0) return old(err);
    sendOptionsResponse(res, options, old);
    });
    }

    // setup basic req values
    req.baseUrl = parentUrl;
    req.originalUrl = req.originalUrl || req.url;

    next();

    function next(err) {
    var layerError = err === 'route'
    ? null
    : err;

    // remove added slash
    if (slashAdded) {
    req.url = req.url.substr(1);
    slashAdded = false;
    }

    // restore altered req.url
    if (removed.length !== 0) {
    req.baseUrl = parentUrl;
    req.url = protohost + removed + req.url.substr(protohost.length);
    removed = '';
    }

    // signal to exit router
    if (layerError === 'router') {
    setImmediate(done, null)
    return
    }

    // no more matching layers
    if (idx >= stack.length) {
    setImmediate(done, layerError);
    return;
    }

    // get pathname of request
    var path = getPathname(req);

    if (path == null) {
    return done(layerError);
    }

    // find next matching layer
    var layer;
    var match;
    var route;

    while (match !== true && idx < stack.length) {
    layer = stack[idx++];
    match = matchLayer(layer, path);
    route = layer.route;

    if (typeof match !== 'boolean') {
    // hold on to layerError
    layerError = layerError || match;
    }

    if (match !== true) {
    continue;
    }

    if (!route) {
    // process non-route handlers normally
    continue;
    }

    if (layerError) {
    // routes do not match with a pending error
    match = false;
    continue;
    }

    var method = req.method;
    var has_method = route._handles_method(method);

    // build up automatic options response
    if (!has_method && method === 'OPTIONS') {
    appendMethods(options, route._options());
    }

    // don't even bother matching route
    if (!has_method && method !== 'HEAD') {
    match = false;
    continue;
    }
    }

    // no match
    if (match !== true) {
    return done(layerError);
    }

    // store route for dispatch on change
    if (route) {
    req.route = route;
    }

    // Capture one-time layer values
    req.params = self.mergeParams
    ? mergeParams(layer.params, parentParams)
    : layer.params;
    var layerPath = layer.path;

    // this should be done for the layer
    self.process_params(layer, paramcalled, req, res, function (err) {
    if (err) {
    return next(layerError || err);
    }

    if (route) {
    return layer.handle_request(req, res, next);
    }

    trim_prefix(layer, layerError, layerPath, path);
    });
    }

    function trim_prefix(layer, layerError, layerPath, path) {
    if (layerPath.length !== 0) {
    // Validate path breaks on a path separator
    var c = path[layerPath.length]
    if (c && c !== '/' && c !== '.') return next(layerError)

    // Trim off the part of the url that matches the route
    // middleware (.use stuff) needs to have the path stripped
    debug('trim prefix (%s) from url %s', layerPath, req.url);
    removed = layerPath;
    req.url = protohost + req.url.substr(protohost.length + removed.length);

    // Ensure leading slash
    if (!protohost && req.url[0] !== '/') {
    req.url = '/' + req.url;
    slashAdded = true;
    }

    // Setup base URL (no trailing slash)
    req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
    ? removed.substring(0, removed.length - 1)
    : removed);
    }

    debug('%s %s : %s', layer.name, layerPath, req.originalUrl);

    if (layerError) {
    layer.handle_error(layerError, req, res, next);
    } else {
    layer.handle_request(req, res, next);
    }
    }
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    这个函数很长,但是其实大部分内容都是匹配路由,类型检测等操作,实际的操作集中在next()函数中,与koa一样,这里也是采用闭包来循环遍历中间件数组。看next()中的执行部分可以看到,正常情况下,实际的操作是由layer.handle_request完成的,下面看layer.js源码:

    //初始化
    function Layer(path, options, fn) {
    if (!(this instanceof Layer)) {
    return new Layer(path, options, fn);
    }

    debug('new %o', path)
    var opts = options || {};

    this.handle = fn;
    this.name = fn.name || '<anonymous>';
    this.params = undefined;
    this.path = undefined;
    this.regexp = pathRegexp(path, this.keys = [], opts);

    // set fast path flags
    this.regexp.fast_star = path === '*'
    this.regexp.fast_slash = path === '/' && opts.end === false
    }
    //处理单元
    Layer.prototype.handle_request = function handle(req, res, next) {
    var fn = this.handle;

    if (fn.length > 3) {
    // not a standard request handler
    return next();
    }

    try {
    fn(req, res, next);
    } catch (err) {
    next(err);
    }
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    bingo,这里就是我们在调用use的时候初始化的Layer结构体,可以看到,实际上我们把中间件函数赋给了layer.handle,而在实际的处理函数handle_request中,正是调用了this.handle,总算找到了数据处理的根源了….
    这里看到实际上router中的next()只是启动了中间件回调的过程,然后把自己传给下一个中间件,后续的中间件主动调用next()这样就可以传递下去了。

    在处理中间件的逻辑上express可以理解为每次一个中间件执行完毕就去主动去通知“中心”去启动下一个中间件;而koa可以理解为链式过程,每一个中间件会启动后一个中间件。

    到此为止,我们基本完成了koa和express的对比分析,二者各有自己的特点,在使用中可以根据需求选择最适合的解决方案。

  • 相关阅读:
    C开发注意事项
    Teamcenter ITK
    Teamcenter SOA开发源码: 打印对象信息
    Teamcenter服务端开发环境配置
    Teamcenter中UID和对象之间相互转化
    Teamcenter 打开数据集
    Teamcenter中SOA调用user service
    63.display:none与visibility:hidden的区别?
    60.为什么要初始化CSS样式
    58.用纯CSS创建一个三角形的原理是什么?
  • 原文地址:https://www.cnblogs.com/ygunoil/p/10953908.html
Copyright © 2020-2023  润新知