解讀express 4.x源碼(1)

這兩天仔細看了看express的源碼,對其的整個實現有了較清晰的認識,因此想總結一下寫出來,若是有什麼不對的地方,望指出。html

這是第一篇,首先介紹一個最簡單的express應用運行過程,初步分析了其在源碼中的具體實現,尚未涉及到一些比較重要的內容好比路由組件的實現方式,中間件的觸發流程等。在後續的總結中,我會繼續分析,並準備將一些值得分析的public api逐一解讀,也會涉及一些private apinode

基於的版本

截止寫這篇文章時目前最新的tags是4.4.2。我是直接看的master分支。express的commits提交很是頻繁,但整體的實現思路應該不會有大的變化。其在4.x後作了較大的改動,相對於3.x最大的地方在於再也不依賴connect,並移除了幾乎全部的內置中間件,具體的變更請看官方wiki的 Migrating from 3.x to 4.xNew features in 4.xgit

從一個官方示例開始

var express = require('express');
var app = express();

app.get('/', function(req, res){
  res.send('Hello World');
});

app.listen(3000);

這是官方給出的一個簡單程序,運行後訪問localhost:3000顯示Hello World。下面咱們就來仔細看看這段程序。github

首先第一行web

var express = require('express');

這是典型的Node.js模塊載入代碼,關於Node.js的模塊載入機制,不瞭解的同窗建議看看樸靈的深刻Node.js的模塊機制,很是有幫助。express

第一行載入了express框架,咱們來看源代碼中的index.jsnpm

module.exports = require('./lib/express');

好吧,還要繼續require,咱們看./lib/express.jsapi

exports = module.exports = createApplication;

從這裏咱們能夠看出,程序的第一行express最後實際是這個createApplication函數。第二行則是運行了這個函數,而後返回值賦給了app。該函數代碼以下數組

var EventEmitter = require('events').EventEmitter;
var mixin = require('utils-merge');
var proto = require('./application');
var req = require('./request');
var res = require('./response');

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'main'函數,其中完成了全部建立express實例所須要的動做,並在執行完畢後返回一個函數。app

代碼的開始定義了一個函數,函數有形參reqresnext爲回調函數。
函數體只有一條語句,執行app.handlehandle方法在application.js文件中定義,此處是經過mixin導入(見下文),handle的代碼以下

app.handle = function(req, res, done) {
  var router = this._router;

  // final handler
  done = done || finalhandler(req, res, {
    env: this.get('env'),
    onerror: logerror.bind(this)
  });

  // no routes
  if (!router) {
    debug('no routes defined on app');

    // generate error    
    var err = new Error('No routes or middlewares have been defined');
    err.status = 500;
    done(err);
    return;
  }

  router.handle(req, res, done);
};

它的做用就是將每對[req,res]進行逐級分發,做用在每一個定義好的路由及中間件上,直到最後完成,具體的過程咱們會在後續進行分析。

而後來看看中間的兩行

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

mixin是在頭部的require處載入的utils-merge模塊,它的代碼以下

exports = module.exports = function(a, b){
  if (a && b) {
    for (var key in b) {
      a[key] = b[key];
    }
  }
  return a;
};

很明顯,mixin(app, proto);的做用便是將proto中全部的property所有導入進appproto在頭部的require處載入的是./lib/application.js文件,其中定義了大部分expresspublic api,如app.set,app.get,app.use...詳見官方的API文檔
mixin(app, EventEmitter.prototype);則將Node.jsEventEmitter中的原型方法所有導入了app。

再來看接下來的兩行

app.request = { __proto__: req, app: app };
app.response = { __proto__: res, app: app };

這裏定義了apprequestresponse對象,使用了對象的字面量表示法,使其分別繼承自req(頂部導入的request.js)和res(頂部導入的response.js),並反向引用了app自身。爲何要這樣作呢?這個問題我一開始想不明白,後來我就乾脆把這兩行代碼刪了,運行,固然就是報錯,答案就在錯誤中的信息裏。

TypeError: Object # has no method 'send'

顯示找不到'send'方法,爲何呢?首先咱們從app.get()方法看起,不熟悉的人會找不到它在源碼中的位置,其實它在application.js中是這樣的

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

    this.lazyrouter();

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

methods在頂部模塊引入中定義,實際上是一個包含各個HTTP請求方法的數組,具體代碼在這裏
從上面的代碼中咱們能夠看到,這裏其實是遍歷了全部methods中定義的方法,固然其中包括get,並且get方法是被'重載'的,即當app.get();的參數只有一個時候,執行的是獲取變量的功能,不然,執行route組件中的route.get方法,將該路由和回調函數(即第二個參數)存儲進一個棧中(後續會進一步分析)。
回到原來的問題,在這裏,關鍵是看中間的

this.lazyrouter();

咱們看它的具體代碼

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._router.use(middleware.init(this));
  }
};

它的做用是在第一次定義路由的時候初始化路由(添加基本的路由),注意最後一句用到了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();
  };
};

它的做用是初始化requestresponse,能夠看到其中用到了我所疑惑app.requestapp.respone,它使reqres繼承自了request.jsresponse.js中的定義,也所以在我去掉了那兩行代碼後會出現res.send找不到的狀況。
另外,定義app.response對象時反引用自身,也使得後面在response對象中可以經過this.app得到所建立的express實例。

讓咱們回到createApplication函數,接下來是app.init();。顯然,做用是初始化,作哪些工做呢?

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

設定了cache對象(render的時候用到),各類setting的存儲對象,engines對象(模板引擎),最後進行默認的配置,代碼有點長這裏就不上了,就是作一些默認的配置。

好了,createApplication函數就是這些,固然,其中略去了不少重要的問題,好比路由組件的實現方式,中間件的觸發流程等,這我會在後續的總結中進行分析。

最開頭的官方示例中還有最後一句

app.listen(3000);

代碼以下

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

其實是調用了Node.js原生的http模塊的CreatServer方法,API文檔說明是

http.createServer([requestListener])#
Returns a new web server object.

The requestListener is a function which is automatically added to the 'request' event.

方法返回的是一個web server對象,其中的參數爲HTTP request事件觸發後執行的函數(這裏咱們給的就是咱們在createApplication函數中得到的app)。
最後,返回的web server有一個監聽端口的listen方法,參數爲須要監聽的端口號,本示例中即爲3000

相關文章
相關標籤/搜索