资讯专栏INFORMATION COLUMN

express4.0源码解析

paraller / 2326人阅读

摘要:就是每一个教程里面开始教学的事例,启动服务器的回调函数。,从入口开始分析源码首先是把模块的属性全部进里面去,在把事件的属性全部进里面去,这是为了给增加事件功能。

express4.X源码解读第一天

express4.X 跟3.X 有很大区别,4.X 去除了connect的依赖,3.X基于connect的中间件基本全部不能用,如果还有可以使用的,也是4.X重写的。所以要想继续使用这些熟悉的中间件,就要手动安装依赖包,或者用一些其他的中间件。

下面开始源码解读 1. express是什么
 typeof express === "function" //true 

可以知道express是个函数,这个函数是程序启动就会运行起来

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

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

  app.request = { __proto__: req, app: app };
  app.response = { __proto__: res, app: app };
  app.init();
  return app;
 }

上面这个函数就是express,有没有看到很熟悉的东西,看到app没,还在哪里看到过这个熟悉的东西...

对了,没错。就是每一个nodejs教程里面开始nodejs教学的事例,nodejs启动服务器:http.createSever 的回调函数。app是express贯穿整个流程的函数。其实整个express 执行过程就是往req,res这两个对象不停的修改属性,添加属性。直到完成请求。中间件也就是通过app做为回调,进而修改req,res。从而实现可插拔的效果。

 var app = express();

这就是为什么引入express,都要开始执行一下这个函数。

2. 程序是如何启动的

express做为一个web框架,首先要有启动一个服务器的,我们看下服务器是在哪里启动的

 var server = app.listen(app.get("port"), function() {
  debug("Express server listening on port " + server.address().port);
 });

express用了一个我不太喜欢用的写法,他把所有的方法直接放到app这个函数上去了,大家都知道函数在js中就是对象,除了本身是可以执行以外,和对象是没有什么区别的。不过这就无形之中增加了阅读代码的难度,而且很容易混淆,因为app既做为一个中间件,还要做为一个公共方法的载体。

好了,讲到启动服务器,app是没有启动服务器的能力的,这个能力是在application 这个文件中被mix进去的,其实就是mix一个http.createServer方法,但是这里还是要看一下代码。

 app.listen = function(){
  var server = http.createServer(this);
  return server.listen.apply(server, arguments);
 };

看到this没有啊,这个this很重要哈,this == app 。app做为回调已经传进来了,神奇的中间件在这里开始了旅程。

3,从入口开始分析源码
 function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };

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

  app.request = { __proto__: req, app: app };
  app.response = { __proto__: res, app: app };
  app.init();
  return app;
}
首先是把application模块的属性全部mix进app里面去,在把事件的属性全部mix进app里面去,这是为了给app增加事件功能。
然后把 req,res模块分别赋值给app,这样this是可以直接调用request,response,具体执行过程还是到了app.init里面去看。
最后把程序实例app返回出去了

好,下面到了application模块的init方法里面去了

 app.init = function(){
  this.cache = {};
  this.settings = {};
  this.engines = {};
  this.defaultConfiguration();
 };

增加了cache setting engines 三个对象,现在看不出来作用,具体执行过程到defaultConfiguration里面看看

 this.enable("x-powered-by")

看到了enable,然后进去看enable其实就set,只不过第二个参数是boolean。set是什么呢?还记得我们没有了解功能的三个对象之一的setting,这个set就是往setting对象添加一些属性而已。

好 先看defaultConfiguration

 this.enable("x-powered-by")

设置x-powered-by 为true,x-powerd-by是什么意思呢?

有些查询工具在我们输入某个站点的URL后就能判断这个站点的WebServer与程序类型。

就是在http请求的时候,能够看到x-powered-by:Express,不设置 就看不到服务区类型,这应该是http请求的一部分

this.set("etag", "weak");

这里处理etag的 Express依赖了一个叫etag的包

var env = process.env.NODE_ENV || "development";
this.set("env", env);
this.set("query parser", "extended");
this.set("subdomain offset", 2);
this.set("trust proxy", false);

这里继续设置属性。

// inherit protos
this.on("mount", function(parent){
  this.request.__proto__ = parent.request;
  this.response.__proto__ = parent.response;
  this.engines.__proto__ = parent.engines;
  this.settings.__proto__ = parent.settings;
});

// setup locals
this.locals = Object.create(null);

// top-most app is mounted at /
this.mountpath = "/";

// default locals
this.locals.settings = this.settings;

// default configuration
this.set("view", View);
this.set("views", resolve("views"));
this.set("jsonp callback name", "callback");

if (env === "production") {
  this.enable("view cache");
}

Object.defineProperty(this, "router", {
  get: function() {
    throw new Error(""app.router" is deprecated!
Please see the 3.x to 4.x migration guide for details on how to update your app.");
  }
});

这里的mount,我之前不知道什么意思,后来看其他应用才知道,这是用来挂载其他应用的,比如我有几个应用,可以起几个业务服务,用一个中央服务监听端口,然后挂载其他几个应用模块

下面研究一下app.use这个方法

研究发现这个时候express的初始化流程已经走完了,以前看过3.X的源码,貌似不是这样子的,但是仔细观察,确确实实到这里是结束了。剩余的方法都是怎么处理的呢?在细细往下看吧

add middleware to the app router

这是源码里面的解释,向路由添加中间件,前面说过中间件和路由没有本质区别,是一样的东西。

app.use = function use(fn) {
 var offset = 0;
 var path = "/";
 var self = this;

 // 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 = self;

   // 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) {
       req.__proto__ = orig.request;
       res.__proto__ = orig.response;
       next(err);
     });
   });

   // mounted an app
   fn.emit("mount", self);
 });

 return this;
  };

于是我们看到lazyrouter这么个东西,这个函数里面new 了一个Router对象,所以这一张暂时略过了 我们要去route里面看看了

昨天看源码遇到了麻烦,发现很多代码还不是那么容易看懂,有些迷糊,然后犯了一些错误,打了很多断点终于弄清楚了

想要明白express的处理流程,必须先要弄清楚app.use和 app.handle这两个方法,这两个方法很重要。

前面我们已经知道app本身是做为回调参数传进http.createServer里面的,应用所有的路由都会掉进这个函数里面去,经过一个一个中间件进行处理。本身想想不是很复杂,但看起代码来还是很蛋疼的

首先req,res被封装了很多方法进去,但是这个方法是在什么地方mix进去的呢。在这里我就犯了个错误,错误的认为会在use的时候就会有这个方法,所以我在use函数里面找啊找,打了很多个断点,始终没有找到哪里执行了这个操作。

但实际上,use始终没有做这个操作,use的作用就是route里面把这个回调push进route实例的stack里面,看代码

if (!fn || !fn.handle || !fn.set) {
      return router.use(path, fn);
    }

app的use执行了 Route实例的use。继续看Route的use

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

    layer.route = undefined;

    self.stack.push(layer);

去看会发现route的use和app的use会有些重复的代码,不同的地方就在于Route的use会创建一个layer。这个layer就是个实例,就是每个回调函数的实例。这个实例包括全局配置的一些属性,比如严格匹配,大小写。还有就是把当前use的路由url和回调存储起来了,全部push进stack里面去。

看下route的实例化过程,会发现express默认放置了两个中间件进去。代码如下

   app.lazyrouter = function() {
     if (!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));
     }
   };

所以app默认就会有两个中间件,query和 middleware。程序执行到这里已经执行结束了。

那又有问题了,request,response这两个对象的很多扩展方法,从何而来。

下面就来看看吧

打开middleware/init

  exports.init = function(app){
    return function expressInit(req, res, next){
      if (app.enabled("x-powered-by")) res.setHeader("X-Powered-By", "Express");
      req.res = res;
      res.req = req;
      req.next = next;

      req.__proto__ = app.request;
      res.__proto__ = app.response;

      res.locals = res.locals || Object.create(null);

      next();
    };
  };

这里就看到了 request,response是在这里被放置到回调的req,res上去的。由于内置的这两个中间件是首先添加的,被放置在stack的前两个,所以每个请求进来首先会进入这两个中间件里面去,然后带了很多东西进入其他的中间件去。

还有问题啊,use不是可以增加路由吗 不是可以控制哪一些中间件走哪一些路由嘛,那是怎么控制的呢。看这里。。。

proto.match_layer = function match_layer(layer, req, res, done) {
  var error = null;
  var path;

  try {
    path = parseUrl(req).pathname;
    if (!layer.match(path)) {
      path = undefined;
    }
  } catch (err) {
    error = err;
  }

  done(error, path);
};

这里会把layer里面存储的route正则拿来和当前路由匹配,成功则进入回调执行,失败则继续执行。

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

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

相关文章

  • vue-router改为history模式

    摘要:打包好后本地测试运行是否正常环境搭建这个时候需要利用中的方法如下安装最新版本中将命令工具分家出来了还需要安装一个命令工具创建一个工程进入项目主目录安装必备包启动程序把打包后的文件夹放在文件夹里访问就能看到项目了这样测试好了后就可以丢后 打包好后本地测试运行是否正常环境搭建: 这个时候需要利用node中的express,方法如下: 安装express: npm install -g ex...

    yunhao 评论0 收藏0
  • 初尝node.js + Express + MongoDB 项目构建(1)

    摘要:前言由于最近公司需要做一个聊天监控的项目,老大让我把后台也做了,于是才真正实践深入的内部。几番折腾终于把项目搭起来了。发生服务特定错误,则前去目录下的和文件删掉,以管理员身份运行命令行然后重新启动服务即可。 前言 由于最近公司需要做一个聊天监控的项目,老大让我把后台也做了,于是才真正实践深入node.js的内部。几番折腾终于把项目搭起来了。 经济基础 node.js (安装配置传送门...

    wangzy2019 评论0 收藏0
  • 初尝node.js + Express + MongoDB 项目构建(1)

    摘要:前言由于最近公司需要做一个聊天监控的项目,老大让我把后台也做了,于是才真正实践深入的内部。几番折腾终于把项目搭起来了。发生服务特定错误,则前去目录下的和文件删掉,以管理员身份运行命令行然后重新启动服务即可。 前言 由于最近公司需要做一个聊天监控的项目,老大让我把后台也做了,于是才真正实践深入node.js的内部。几番折腾终于把项目搭起来了。 经济基础 node.js (安装配置传送门...

    toddmark 评论0 收藏0
  • Webpack + Angular的组件化实践

    摘要:最近写复旦二手平台的时候开始尝试用一直推崇了很久的组件化。经过一番抉择之后选择了的组合。所以在这里分享一下具体的实践流程。自己有自己独特的依赖注入以及模块声明方式,看起来似乎和是水火不容的,但事实上他们完全可以融合。 最近写复旦二手平台的时候开始尝试用一直推崇了很久的组件化。经过一番抉择之后选择了 webpack + angular 的组合。所以在这里分享一下具体的实践流程。 Web...

    neuSnail 评论0 收藏0
  • Flink Metrics 源码解析

    摘要:有如下模块源码解析源码解析源码解析源码解析源码解析源码解析源码解析源码解析源码解析使用和监控和博客从到学习介绍从到学习上搭建环境并构建运行简单程序入门从到学习配置文件详解从到学习介绍从到学习如何自 Flink Metrics 有如下模块: Flink Metrics 源码解析 —— Flink-metrics-core Flink Metrics 源码解析 —— Flink-metr...

    sshe 评论0 收藏0

发表评论

0条评论

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