express4.0源碼解析

express4.X源碼解讀第一天

express4.X 跟3.X 有很大區別,4.X 去除了connect的依賴,3.X基於connect的中間件基本所有不能用,若是還有可使用的,也是4.X重寫的。因此要想繼續使用這些熟悉的中間件,就要手動安裝依賴包,或者用一些其餘的中間件。node

下面開始源碼解讀

1. express是什麼

typeof express === 'function' //true

能夠知道express是個函數,這個函數是程序啓動就會運行起來web

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沒,還在哪裏看到過這個熟悉的東西...express

對了,沒錯。就是每個nodejs教程裏面開始nodejs教學的事例,nodejs啓動服務器:http.createSever 的回調函數。app是express貫穿整個流程的函數。其實整個express 執行過程就是往req,res這兩個對象不停的修改屬性,添加屬性。直到完成請求。中間件也就是經過app作爲回調,進而修改req,res。從而實現可插拔的效果。json

var app = express();

這就是爲何引入express,都要開始執行一下這個函數。服務器

2. 程序是如何啓動的

express作爲一個web框架,首先要有啓動一個服務器的,咱們看下服務器是在哪裏啓動的app

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方法,可是這裏仍是要看一下代碼。dom

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

看到this沒有啊,這個this很重要哈,this == app 。app作爲回調已經傳進來了,神奇的中間件在這裏開始了旅程。ide

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!\nPlease 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正則拿來和當前路由匹配,成功則進入回調執行,失敗則繼續執行。

相關文章
相關標籤/搜索