express原始碼學習

PlayerWho發表於2018-03-18

I don't read books, never went to school, I just read other people's code and always wonder how things work. ——TJ Holowaychuk

簡介

這篇文章的主要的目的是通過研究express核心原始碼,讓我們對express深入理解,不僅會用express,還要理解其背後的思想,提高開發效率。研究express原始碼,學習大神的程式碼結構。本文只介紹核心程式碼和核心流程,型別判斷和express的使用等不包括在內。

express

express裡的核心檔案是index、express、application、router/index、router/layer、router/route。 index裡只有一句話

module.exports = require('./lib/express');
複製程式碼

匯入express,並匯出。express檔案裡是匯出許多api,像express、express.Router等。我們開發是用到的express(),實際上是執行createApplication()。application裡是和app相關的api。 router/index裡是和router相關的程式碼,router可以理解成路由器,把各個請求發給route。我們不會直接呼叫router/layer裡的方法,layer是一個抽象概念,在express裡中介軟體、路由都放在app._router.stack裡,stack裡的每個元素就是一個layer。 route裡也有一個stack,裡面的元素也是layer。

?

  • 從下面的程式碼開始對express原始碼的研究:
  • 用express做一個簡單的伺服器,訪問http://localhost:3000,返回"Hello World"
const express = require('express');
const app = express();
app.get('/', (req, res,next)=>{
    res.send('Hello World');
    next()
});
app.listen(3000,()=>{
    console.log('server is ok');
});
複製程式碼
  • express(),實際呼叫createApplication(),返回一個app函式。
function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };
  //把proto的方法給app等初始化操作。
  return app;
}
複製程式碼
  • 這個app上有"application.js"的匯出物件proto上的所有方法。proto在"application.js"裡命名app,為了方便,下文都成為app。
  • app上有一個lazyrouter()方法,改方法主要是判斷app._router是否存在,如果不存在new Router賦值給app._router。
  • app.get裡的核心程式碼如下:
  app.get = function(path){
    this.lazyrouter();
    var route = this._router.route(path);
    route[method].apply(route, slice.call(arguments, 1));
    return this;
  };
複製程式碼
  • route裡是真正處理請求回撥的函式,在route[method]裡,迴圈引數,每次迴圈新建一個layer,handle是app.get的回撥,把layer放在route的stack裡。route[method]裡的核心程式碼是:
 var layer = Layer('/', {}, handle);
 layer.method = method;
 this.methods[method] = true;
 this.stack.push(layer);
複製程式碼
  • this._router即Router的例項。this._router.route(path)這個方法的核心程式碼如下:
proto.route = function route(path) {
  var route = new Route(path);
  var layer = new Layer(path, {}, route.dispatch.bind(route));
  layer.route = route;
  this.stack.push(layer);
  return route;
};
複製程式碼
  • route方法裡新建了一個Route和Layer,Layer的第三個引數handle,是express中介軟體執行的核心內容(個人想法,歡迎討論)。原始碼中可以看到,layer放到了this.stack,其實就是app._router.stack。 app._router.stack裡存放著中介軟體。最後返回route。app.get執行結束,下面是app.listen:
app.listen = function listen() {
  var server = http.createServer(this);
  return server.listen.apply(server, arguments);
};
複製程式碼
  • listen裡是監聽建立一個server,把引數傳給server.listen,createServer的回撥是this。我們從createApplication裡可以看到,現在的app是一個函式,所以請求來了,執行app.handle。app.handle裡實際是執行了this._router.handle(req, res, done), express裡用到了很多代理模式。在router.handle裡,處理一些請求的url和params,呼叫內部的next方法,從router.stack裡找到和請求匹配的layer,最終呼叫layer.handle_request方法,並把next作為引數傳入。 layer.handle_request裡呼叫this.handle,this.handle是Layer的第三個引數route.dispatch.bind(route)。在dispatch裡執行next找到stack裡的layer,執行layer.handle_request,並把next傳入。layer.handle_request執行handle,即app.get的回撥函式。
  • 初始化
    express原始碼學習
  • get方法
    express原始碼學習
  • 發起請求
    express原始碼學習

路由

在express裡建立路由主要由這幾種方法:

  • app.METHODS
  • app.route().get().post()
  • app.all()
  • express.Router(),這個和上面的方法有一點不一樣。需要app.use(path,router)才能使用。下文會給出詳細的資料結構 這裡面的核心程式碼是:
this.lazyrouter();
var route = this._router.route(path);
route[methods](fn);
複製程式碼
  • express建立路由,實際上是先呼叫_router.route(),再呼叫route.METHODS。
proto.route = function route(path) {
  var route = new Route(path);
  var layer = new Layer(path, {}, route.dispatch.bind(route));
  layer.route = route;
  this.stack.push(layer);
  return route;
};
Route.prototype[method] = function(){
    //把引數轉化成陣列 handles
    for (var i = 0; i < handles.length; i++) {
      var handle = handles[i];
      var layer = Layer('/', {}, handle);
      layer.method = method;
      this.methods[method] = true;
      this.stack.push(layer);
    }
    return this;
  };
複製程式碼
  • 從 layer.route = route;可以得出路由是掛載layer上的。
//Route的資料結構
{
    methods:{},
    path:path,
    stack:[
        Layer{
        handle:handle
        method:method
        ...
        }
    ]
}
複製程式碼

中介軟體

  • 中介軟體分為:應用級中介軟體、路由級中介軟體、錯誤處理中介軟體、內建中介軟體、第三方中介軟體。
  • 錯誤處理中介軟體和其他中介軟體的區別是回撥函式有四個引數,第一個引數是錯誤物件。
  • 中介軟體的使用有兩種:掛載在app、掛載在express.Router()。
  • app.use裡最終呼叫router.use,router.use的核心程式碼:
var layer = new Layer(path, {}, fn);
layer.route = undefined;
this.stack.push(layer); //app._router.stack.push(layer)
複製程式碼
  • app.use和app.METHOD,建立的中介軟體的資料結構是不一樣的。
//app.use建立的layer
Layer{
    route:undefined,
    handle:fn
}
//app.get建立的layer
Layer{
    route:route,
    handle:route.dispatch.bind(route)
}
複製程式碼
  • 用app.use呼叫,一個用express.Router()建立的路由,即app.use(router),資料結構變為:
Layer{
    route:undefined,
    handle:router
}
複製程式碼
  • 如果路由中介軟體呼叫路由中介軟體,router.use(router.use(router.get(path))),最終被app.use(router)執行。流程圖如下
    express原始碼學習
  • express中介軟體可以抽象成下面的樣子
    express原始碼學習
  • Router的例項是一個完整的中介軟體和路由系統,因此常稱其為一個 “mini-app”。app的use和定義路由方法很多都是通過Router實現的。
  • app、Router、Route、Layer的主要資料結構可以用下圖表示

express原始碼學習

總結

  • 路由和中介軟體是express的核心,學會路由和中介軟體,再學express其他相關的會事半功倍。
  • 閱讀原始碼,不僅知道了express的原理,還從程式碼中學到了用代理模式的優點,一處實現,多處呼叫,職責單一,改動小。
  • 從使用api的角度一步步"解刨"原始碼,推匯出作者的思想及資料結構。如果我們從作者的思想及資料結構,和api的設計,反推出原始碼的實現,可能閱讀原始碼時效率會更高。

參考

相關文章