【Express】源碼閱讀記錄(一)

雖然說如今能用戶 express 搭建簡單的靜態服務器,作一些簡單的數據交互,可是老是有一種知其然不知其因此然的感受。自己對nodejs的瞭解也不夠。正好花些時間來閱讀如下源碼,本文基於Express 4.15.4html

Express主要目錄架構

項目目錄架構選了最主要的一部分列了出來前端

├── lib
|   ├── middleware        
|   |   ├── init.js
|   |   └── query.js
├── ├── router        //router組件,負責中間件的插入和鏈式執行
|   |   ├── index.js
|   |   ├── layer.js
|   |   └── route.js
├── ├── application.js
├── ├── express.js
├── ├── request.js   //提供一些方法豐富 request的實例功能
├── ├── reponse.js
├── ├── util.js
├── └── view.js      //提供模板渲染引擎的封裝,經過 res.render() 調用引擎渲染網頁
|
└── index.js

express和app

在express中出現頻率最高的是 expressapp 可見其重要性。舉個例子,搭建一個最簡單的靜態服務器node

const express = require('express')
const app = express()

app.get('/',function(req,res){
    res.send('Hello World!')
    res.end()
})
app.listen(3000,function(){
    console.log('服務器已啓動...')
})

接下來在源碼中分析express和appgit

/*
 * express.js
 */
//調用express()返回的app實際上是一個函數
exports = module.exports = createApplication
//createApplication就至關於express的 main 函數,其中完成了全部建立express實例所須要的動做
function createApplication(){
    var app = function(req,res,next){
        app.handle(req,res,next)
    }
    
    //此處是經過mixin導入
    mixin(app,EventEmitter.protprype,false)
    mixin(app,proto,false)
    
    app.request = Object.create(req,{...})x
    app.response = Object.create(res,{...})
    
    return app
}

上述代碼中的app.handle,handle方法是在application.js中定義的。首先看一下application.js的總體架構github

//application.js
app.init = function init(){
    this.cache = {}
    this.engines = {}
    this.settings = {}
    
    this.defaultConfiguration()
}

//app.相關方法

app.render = function render(name,options,callback){...}  

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

//容錯處理
function logederror(err){...}
function tryRender(view,options,callback){...}

app是一個請求處理函數,做爲http.createServer的參數。而express實際上是一個工廠函數,用來生成請求處理函數。
爲何說express是一個工廠函數?搭建靜態服務器的時候的一行代碼能夠來證實:web

const app = express()

接下來將 app.handle() 單獨提出來研究一下:
在express.js中是經過mixin導入的。其做用是將每對[req,res]進行逐級分發,做用在每一個定義好的路由及中間件上,直到最後完成express

app.handle = function(req,res,callback){
    var router = this._router
    
    //final handler
    var done = callback||finalhandler(req,res,{
        env:this.get('env'),
        onerror:logerror.bind(this)
    })
    
    //no routes
    if(!router){
        debug('no routes defined on app')
        done()
        return
    }
    router.handle(req,res,done)
}

這裏引伸除了兩個關鍵點 中間件路由編程

中間件 Middleware

在express中最關鍵的概念就是中間件了。一個Express應用從本質上來講就是一系列的中間件調用。那麼到底什麼是中間件?數組

一箇中間件本質上就是一個函數,調用中間件就是對函數的調用。在Express4.x中取消了全部內置的中間件,須要從外部引入。這樣的改進是的express 是一個獨立的路由和web中間件框架,更新更加方便更加符合unix哲學服務器

中間件通常函數形式以下:

function middleware(req,res,next){
    //...
}

異常處理是異步編程的一個難點,咱們不能用常規的 try-catch 語句塊去捕獲異常。try-catch只能捕獲當次事件循環內的異常,對callback執行拋出的異常無能爲力。因此Node在處理異常上造成一種約定,將異常做爲回調函數的第一個實參傳回,若是爲空值,則代表沒有異常拋出
處理錯誤中間件函數形式:

function middleware(err,req,res,next){
    //error做爲函數的
    //...
}

接下來看一下中間件函數中的參數
忽略req 和 res , next 自己也是一個函數,調用 next() 就會繼續執行下一個中間件。請求過程圖解以下:

↓
---------------
| middleware1 |
---------------
       ↓
---------------
| ... ... ... |
---------------
       ↓
---------------
| middlewareN |
---------------
       ↓

在使用express時咱們常常會看到這樣的代碼:

//匹配全部以/user開始的路徑
app.use('/user',function(req,res,next){...})

//精確匹配到/user路徑
app.get('/user',function(req,res,next){...})

根據上述代碼,中間件大體能夠分爲兩種:普通中間件路由中間件 。註冊普通中間件一般是經過 app.use() , 註冊路由中間件,一般是經過 app.METHOD()來註冊。
而這兩種方法其實都是由 app.handle 來處理

路由 Router

要想了解請求處理的詳細過程,首先須要瞭解Router。先來看一下Routerde 目錄結構:

├── router      
|   ├── index.js
|   ├── layer.js
|   └── route.js

簡單來講,Router(源碼在router/index.js)就是一箇中間件的容器。事實上,Router是Express中一個很是核心的東西,基本上就是一個簡化版的Express框架。app的不少API,例如:app.use(),app.param(),app.handle()等,事實上都是對Router的API的一個簡單包裝。能夠經過app._router來訪問默認的Router對象。

app.get()是如何實現的

methods.forEach(function(method){
    //在JavaScript能夠經過 '[]' 或 '.'來訪問對象屬性.
    //當咱們不僅屬性內容,或者屬性內容是個變量時能夠用'[]'
    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
    }
})

經過上述代碼咱們能夠發下,Express對 METHOD 方法的添加都是動態的。一個forEach循環搞定了全部method函數的定義。
看下函數的內部實現,若是函數參數長度爲 1 ,那麼直接返回this.set(path)。查看Express API能夠發現:app.get() 實現了兩個功能,若是長度爲1,則返回app.set()定義的變量,若是參數長度大於1 ,則進行路由處理。

接着, this.lazyrouter() ,定位到源碼

app.lazyrouter = function lazyrouter(){
    if(!this._router){    //若是_router不存在,就new一個Router出來
        this._router = new Router({
            caseSensitive: this.enabled('case sensitive routing'),
            strict: this.enabled('strict routing')
        })
        this._router.use(query(this.get('query parse fn')))
        this._router.use(middleware.init(this))
    }
}

這個new出來的Router其本質上是一箇中間件容器。而且 Router是Express中一個很是核心的東西,基本上就是一個簡化版的Express框架 。app的不少API,例如:app.use(),app.param(),app.handle()等,事實上都是對Router的API的一個簡單包裝。能夠經過app._router來訪問默認的Router對象。

返回以前的 methods.forEach ,發現當執行完 this.lazyrouter() 以後
Route能夠簡單的理解爲存放路由處理函數的容器,它有一個 stack 屬性爲一個數組,其中的每一項是一個Layer對象,是對路由處理函數的包裝。

var route = this._router.route(path)

根據上述代碼能夠定位到 router/index.js

proto.route = function route(path){
    var route = new Route(path)
    
    var layer = new Layer(path,{
        sensitive:this.caseSensitive,
        strict:this.strict,
        end:true
    },route.dispatch.bind(route))
    
    layer.route = route
    
    this.stack.push(layer)
    return route
}

這裏new了一個 route 對象和一個 layer 對象,而後將route 對象賦值給 layer.route

Route模塊對應的是route.js,主要是來處理路由信息的,每條路由都會生成一個Route實例。而Router模塊對應的是index.js,Router是一個路由的集合,在Router模塊下能夠定義多個路由,也就是說,一個Router模塊會包含多個Route模塊。經過上邊的代碼咱們已經知道,每一個express建立的實例都會懶加載一個_router來進行路由處理,這個_router就是一個Router模塊。

那麼route 是如何具體處理路由

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

和application.js中處理方式是相同的。當調用 route.MEHOD() 的時候,新建一個layer 放在 route.stack

經過上面的分析能夠發現,Router實際上是一個二維的結構。例如,一個可能的router.stack結構以下所示:

----------------
|    layer1    |
----------------
        ↓
---------------- layer2.route.stack  ------------   ------------   ------------
|    layer2    | ------------------> | layer2-1 |-->| layer2-2 |-->| layer2-3 |
----------------                     ------------   ------------   ------------
        ↓
---------------- layer3.route.stack  ------------   ------------
|    layer3    | ------------------> | layer3-1 |-->| layer3-2 |
----------------                     ------------   ------------
        ↓
----------------
|    ......    |
----------------
        ↓
----------------
|    layerN    |
----------------

綜上所述,咱們發現一個路由中間件註冊的過程大體爲:app.METHOD-->router.route-->route.METHOD
而其中最重要的是經過 router.route() 來建立一條新路由,而後調用 route.METHOD()來註冊相關函數

其餘參考連接

源碼地址

Express 4.15.4

參考連接4

  1. 前端亂燉
  2. 從express源碼探析其路由機制
  3. 解讀EXPRESS 4.X源碼
  4. 樸零 《深刻淺出Node.js》
  5. express源碼分析之Router
相關文章
相關標籤/搜索