资讯专栏INFORMATION COLUMN

探讨Express Router & Route

oysun / 2364人阅读

摘要:接下来通过研究源码,来探讨路由原理的实现。类保存和一些数据信息相同点都是存放挂载路径用来判断是否是路由中间件。放在里的路由中间件,通过指向,与相关联起来

Express
基于 Node.js 平台,快速、开放、极简的 web 开发框架
安装
//应用生成器工具
npm install express-generator -g

//创建express应用包
express app

//安装依赖
npm install

成功生成后,会产生以下的目录和文件:

|---bin
|---node_module
|---public
|---routes
|---view
|---app.js
|---package.json

接下来我们通过:

npm start 

启动程序后,访问127.0.0.1:3000,就能访问到express的页面了。

接下来通过研究源码,来探讨express路由原理的实现。

路由

我们通过查看app.jsindex.js文件:

app.js

var index = require("./routes/index");

app.use("/", index);

//或
app.get("/", index);

routes/index.js

var express = require("express");
var router = express.Router();

router.get("/", function(req, res, next) {
  res.render("index", { title: "Express" });
});

可以看出,express的路由大概实现 定义一份路由规则文件,再通过app.use()或者app[METHOD]来建立路由规则访问联系,虽然两者的结果一样,但是存在本质上的区别。

下图是主要涉及的几个文件:

接下来我们通过源码首先看看app.use()具体是一个什么样实现思路。

app.use

我们打开node_module里的express文件夹。打开lib/application.js文件。

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

看到use里部分的代码,开始做了判断处理use挂载的是路径还是function,并且通过lazyrouter()方法实例router类,并且全局只存在一个router实例对象,最终调用router.use()方法。

接着,我们到lib/router/index.js 看router.use方法的实现:

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

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

    layer.route = undefined;

    this.stack.push(layer);
  }

  return this;
};

通过对比app.use方法,router.use前半部分处理相同,但后面实例化一个Layer类,并且丢进stack里。

Layer类保存Router和Route一些数据信息:

相同点:

path都是存放挂载路径,options.end用来判断是否是路由中间件。

不同点:

Router和Route的区别是一个是添非路由中间件,另一个是添加路由中间件。

他们的layer.route指向也不一样,一个指向undefined,另一个没有route属性。

文章进行到一半,我们小总结一下,app.use()方法是用来添加非路由中间件的,最终是调用router实例方法,会实例划一个Layer类对象用于存放数据,并且把layer对象push进router.stack里,全局只有一个router。

app[METHOD]

我们通过源码去探讨路由中间件app[METHOD]是一个怎样的原理:

app.jsapp.use("",index)改成app.get("",index).

application.js:

methods.forEach(function(method) {
    app[method] = function(path) {
        if (method === "get" && arguments.length === 1) {
            // app.get(setting)
            return this.set(path);
        }

        this.lazyrouter();

        var route = this._router.route(path);
        route[method].apply(route, slice.call(arguments, 1));
        return this;
    };
});

可以看出,代码里做了一个app.get方法的判断处理,get方法只有一个参数时,是获取app的本地变量,后面还是实例化router对象,并且用router上的route方法放回的对象去调用Route类上的route[METHOD].

/lib/router/route.js

methods.forEach(function(method){
  Route.prototype[method] = function(){
    var handles = flatten(slice.call(arguments));

    for (var i = 0; i < handles.length; i++) {
      var handle = handles[i];

      if (typeof handle !== "function") {
        var type = toString.call(handle);
        var msg = "Route." + method + "() requires callback functions but got a " + type;
        throw new Error(msg);
      }

      debug("%s %o", method, this.path)

      var layer = Layer("/", {}, handle);
      layer.method = method;

      this.methods[method] = true;
      this.stack.push(layer);
    }

    return this;
  };
});

在route里有一个实例化的layer,且放在stack里,与Router的layer不同的是,Route的没有layer.route且layer.method存放http方法。

到这里,我们大概可以总结下路由中间件和非路由中间件的联系,如下图:

app初始化时,会push两个方法(init,query)进router.stack里。我们可以通过app.use往app添加非路由中间件,也可以通过app[METHOD]添加路由中间件,同样是push layer实例对象,但route是指向Route实例化的对象。

完整的Router逻辑过程,如图:

总结

express中添加中间件方法有app.use和app[METHOD],当然还有内置的Router类,app.use用来添加非路由中间件,app[METHOD]用来添加路由中间件。

Layer类封装中间的path和handle(fns的处理)

Router和Route都有对应的stack,但是Route在整个app中只有一个,而Route可以又多个。放在Router
stack里的路由中间件,通过Layer.route指向Route,与Route stack相关联起来

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

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

相关文章

  • Sails.js 内存暴涨 &amp; 源码分析

    摘要:是下的一个优秀的框架,但是使用后,在流量增长时,进程有时突然内存暴涨保持高占用。如果是内存泄露引起的,则需要细心检查代码,确定变量能正常回收。每个对象有自己产生的内存。译注但是大对象内存区本身不是可执行的内存区。 Sails.js 是 node 下的一个优秀的 MVC 框架,但是使用 Sails 后,在流量增长时, node 进程有时突然内存暴涨、保持高占用。经过翻阅源码后,发现这个问...

    antz 评论0 收藏0
  • Express源码学习-路由篇

    摘要:框架核心特性路由定义了路由表用于执行不同的请求动作。中间件可以设置中间件来响应请求。注册一个请求路由结束响应开启监听端口执行上面代码是一种实用工具,将为您的源的任何变化并自动重启服务器监控。 Express 简介 Express 是一个简洁而灵活的 node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。使用 Express 可以快...

    laznrbfe 评论0 收藏0
  • express 的 middleware 设计

    摘要:入口文件在文件夹下的,其向外界暴露了一些方法。方法也是从中继承的。入口文件很清晰,主要是完成方法的暴露以及的一些初始化操作。下一篇写写路由的实现。 还没用express写过server,先把部分源码撸了一遍,各位大神求轻拍。 express入口文件在lib文件夹下的express.js,其向外界暴露了一些方法。 最主要的(express.js 第36-47行): function cr...

    zollero 评论0 收藏0
  • 笔记:解读express 4.x源码

    摘要:载入了框架,我们来看源代码中的。函数函数代码如下代码的开始定义了一个函数,函数有形参,,为回调函数。相应的,等同于继承,从而让有了事件处理的能力。 此为裁剪过的笔记版本。 原文在此:https://segmentfault.com/a/11...原文在此: https://cnodejs.org/topic/574... 感谢@YiQi ,@leijianning 带来的好文章。我稍作...

    jzman 评论0 收藏0
  • express 源码阅读(全)

    摘要:每个请求都会对应一个响应。一个响应主要包括状态行响应头消息体,将常用的数据封装为类,在上面的代码中就是该类的一个对象。执行测试用例,报错,提示不存在。目前在中,一个路由是由三个部分构成路径方法和处理函数。 1. 简介 这篇文章主要的目的是分析理解express的源码,网络上关于源码的分析已经数不胜数,这篇文章准备另辟蹊径,仿制一个express的轮子,通过测试驱动的开发方式不断迭代,正...

    Steven 评论0 收藏0

发表评论

0条评论

oysun

|高级讲师

TA的文章

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