资讯专栏INFORMATION COLUMN

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

zilu / 1896人阅读

摘要:目前使用人数众多。通过利用函数,帮你丢弃回调函数,并有力地增强错误处理。这个系列的博客主要讲解和的中间件机制,本篇将主要讲解的中间件机制。其中间件机制的核心为内部方法的实现。

提到 Node.js 开发,不得不提目前炙手可热的2大框架 Express 和 Koa。  

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

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

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

Express 中间件

connect 曾经是 express 3.x 之前的核心,而 express 4.x 已经把 connect 移除,在 express 中自己实现了 connect 的接口,所以我们本篇中的源码解释将直接使用 connect 源码。

示例

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

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()

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);

输出结果为

第一个中间件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 方法,直至所有中间件挂载完毕,当然这过程中会做一些异常、兼容等的处理。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102523.html

相关文章

  • express分析和对比

    摘要:前言目前最新版本是所以本文分析也基于这个版本。源码分析直接切入主题由于目前是一个独立的路由和中间件框架。所以分析的方向也以这两个为主。源码去年的时候有分析过现在对比分析思考下。 前言 目前express最新版本是4.16.2,所以本文分析也基于这个版本。目前从npm仓库上来看express使用量挺高的,express月下载量约为koa的40倍。所以目前研究下express还是有一定意义...

    mmy123456 评论0 收藏0
  • koa间件机制详解

    摘要:在我眼中,的确是比轻量的多,给我的感觉更像是一个中间件框架,只是一个基础的架子,需要用到的相应的功能时,用相应的中间件来实现就好,诸如路由系统等。基于的库,所以利用来代替回调,而由于对的支持,所以利用的是。 koa koa是由express原班人马打造的一个更小、更富有表现力、更健壮的web框架。 在我眼中,koa的确是比express轻量的多,koa给我的感觉更像是一个中间件框架,k...

    Bmob 评论0 收藏0
  • Koa-middleware实现机制分析

    摘要:现在我们从实现一个简易的方法开始探索其中的机制。其中内部的可以将上一个的返回值传递给外部。一言以蔽之实现了递归调用的方法。当执行到的中间件没有时并且返回的为时逆序执行。 本文发布在github.com/ssssyoki,欢迎star,issues共同交流。 Koa是基于Node.js的下一代web开发框架,相比Express更轻,源码只有几百行。与传统的中间件不同,在Koa 1.x中采...

    MageekChiu 评论0 收藏0
  • koa2 网打尽(基本使用,洋葱圈,间件机制和模拟,源码分析(工程,核心模块,特殊处理),核心点

    摘要:洋葱圈处理模型。基于的灵活强大的中间件机制。参考官网提供的基本,不在赘述部分实现,参考源码分析常用服务端口监听返回适用于方法的回调函数来处理请求。 本文 github 地址: https://github.com/HCThink/h-blog/blob/master/source/koa2/readme.md github 首页(star+watch,一手动态直达): https:...

    william 评论0 收藏0
  • 再谈expresskoa的对比

    摘要:以前其实写过一篇和的对比但是后来发现里面有不少谬误所以一直惦记着纠正一下之前的错误尤其关于中间件部分的对比这里的就拿更加简单的代替的执行流程通常我们都说的中间件模型是线性的也就是一个一个往下执行的如下图这么说当然是没错的但是当我们执行下面代 以前其实写过一篇express和koa的对比, 但是后来发现里面有不少谬误. 所以一直惦记着纠正一下之前的错误, 尤其关于中间件部分的对比. 这里...

    phodal 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<