Express与Koa中间件机制分析(一)

提到 Node.js 开发,不得不提目前煊赫一时的2大框架 Express 和 Koa。javascript

Express 是一个保持最小规模的灵活的 Node.js Web 应用程序开发框架,为 Web 和移动应用程序提供一组强大的功能。目前使用人数众多。java

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 经过利用 async 函数,Koa 帮你丢弃回调函数,并有力地加强错误处理。 Koa 并无捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。git

相信对这两大框架有一些了解的人都或多或少的会了解其中间件机制,Express 为线型模型,而 Koa 则为洋葱型模型。这个系列的博客主要讲解 Express 和 Koa 的中间件机制,本篇将主要讲解 Express 的中间件机制。github

Express 中间件

connect 曾经是 express 3.x 以前的核心,而 express 4.x 已经把 connect 移除,在 express 中本身实现了 connect 的接口,因此咱们本篇中的源码解释将直接使用 connect 源码。web

示例

下面将使用 Express 实现一个简单的 demo 来进行中间件机制的讲解express

var express = require('express');

var app = express();
app.use(function (req, res, next) {
    console.log('第一个中间件start');
    setTimeout(() => {
        next();
    }, 1000)
    console.log('第一个中间件end');
});
app.use(function (req, res, next) {
    console.log('第二个中间件start');
    setTimeout(() => {
        next();
    }, 1000)
    console.log('第二个中间件end');
});
app.use('/foo', function (req, res, next) {
    console.log('接口逻辑start');
    next();
    console.log('接口逻辑end');
});
app.listen(4000);
复制代码

此时的输出比较符合咱们对 Express 线性的理解,其输出为数组

第一个中间件start
第一个中间件end
第二个中间件start
第二个中间件end
接口逻辑start
接口逻辑end
复制代码

可是,若是咱们取消掉中间件内部的异步处理直接调用 next()bash

var express = require('express');

var app = express();
app.use(function (req, res, next) {
    console.log('第一个中间件start');
    next()
    console.log('第一个中间件end');
});
app.use(function (req, res, next) {
    console.log('第二个中间件start');
    next()
    console.log('第二个中间件end');
});
app.use('/foo', function (req, res, next) {
    console.log('接口逻辑start');
    next();
    console.log('接口逻辑end');
});
app.listen(4000);
复制代码

输出结果为app

第一个中间件start
第二个中间件start
接口逻辑start
接口逻辑end
第二个中间件end
第一个中间件end
复制代码

这种结果不是和 Koa 的输出很类似吗?是的,可是它和剥洋葱模型仍是不同的,其实这种输出的结果是因为代码的同步运行致使的,并非说 Express 不是线性的模型。框架

当咱们的中间件内没有进行异步操做时,其实咱们的代码最后是如下面这种方式运行的

app.use(function middleware1(req, res, next) {
    console.log('第一个中间件start')
        // next()
        (function (req, res, next) {
            console.log('第二个中间件start')
                // next()
                (function (req, res, next) {
                    console.log('接口逻辑start')
                        // next()
                        (function handler(req, res, next) {
                            // do something
                        })()
                    console.log('接口逻辑end')
                })()
            console.log('第二个中间件end')
        })()
    console.log('第一个中间件end')
})
复制代码

致使这种运行方式的其实就是 connect 的实现方式,接下来咱们进行其源码的解析

connect 源码解析

connect的源码仅有200多行,可是这里讲解只选择其中部分核心代码,并不是完整代码,查看所有代码请移步 github

中间件的挂载主要依赖 proto.use 和 proto.handle,这里咱们删掉部分 if 判断以使咱们更专一于其内部原理的实现

proto.use = function use(route, fn) {
    var handle = fn;
    var path = route;

    // 这里是对直接填入回调函数的进行容错处理
    // default route to '/'
    if (typeof route !== 'string') {
        handle = route;
        path = '/';
    }
    .
    .
    .
    this.stack.push({ route: path, handle: handle });

    return this;
};
复制代码

proto.use 主要将咱们须要挂载的中间件存储在其自身 stack 属性上,同时进行部分兼容处理,这一块比较容易理解。其中间件机制的核心为 proto.handle 内部 next 方法的实现。

proto.handle = function handle(req, res, out) {
    var index = 0;
    var stack = this.stack;

    function next(err) {

        // next callback
        var layer = stack[index++];

        // all done
        if (!layer) {
            defer(done, err);
            return;
        }

        // route data
        var path = parseUrl(req).pathname || '/';
        var route = layer.route;

        // skip this layer if the route doesn't match
        if (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) {
            return next(err);
        }

        // call the layer handle
        call(layer.handle, route, err, req, res, next);
    }

    next();
};
复制代码

在删除到部分非核心代码后,能够清晰的看到,proto.handle 的核心就是 next 方法的实现和递归调用,对存在于 stack 中的中间件取出、执行。

这里即可以解释上文中异步和非异步过程当中所输出的结果的差别了。

  • 当有异步代码时,将会直接跳过继续执行,此时的 next 方法并未执行,须要等待当前队列中的事件所有执行完毕,因此此时咱们输出的数据是线性的。
  • 当 next 方法直接执行时,本质上全部的代码都已经为同步,因此层层嵌套,最外层的确定会在最后,输出了相似剥洋葱模型的结果。

总结

connect 的实现其基本原理是维护一个 stack 数组,将所须要挂载的中间件处理后所有 push 到数组内,以后在数组内循环执行 next 方法,直至全部中间件挂载完毕,固然这过程当中会作一些异常、兼容等的处理。

后续

Express与Koa中间件机制分析(二) 主要分析 Koa2 的中间件实现机制

相关文章
相关标签/搜索