成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

Express源碼學(xué)習(xí)-路由篇

laznrbfe / 2232人閱讀

摘要:框架核心特性路由定義了路由表用于執(zhí)行不同的請求動作。中間件可以設(shè)置中間件來響應(yīng)請求。注冊一個請求路由結(jié)束響應(yīng)開啟監(jiān)聽端口執(zhí)行上面代碼是一種實用工具,將為您的源的任何變化并自動重啟服務(wù)器監(jiān)控。

Express 簡介

Express 是一個簡潔而靈活的 node.js Web應(yīng)用框架, 提供了一系列強大特性幫助你創(chuàng)建各種 Web 應(yīng)用,和豐富的 HTTP 工具。
使用 Express 可以快速地搭建一個完整功能的網(wǎng)站。

Express官網(wǎng) 也是一個非常友好的文檔。

Express 框架核心特性:

路由: 定義了路由表用于執(zhí)行不同的 HTTP 請求動作。

中間件: 可以設(shè)置中間件來響應(yīng) HTTP 請求。

模板引擎: 可以通過向模板傳遞參數(shù)來動態(tài)渲染 HTML 頁面。

這里先從Express路由開始學(xué)習(xí)解析,再繼續(xù)它的其他核心特性進行學(xué)習(xí)與探索。
安裝 Express (V4.16.2)
npm i express -S
Express簡單使用

1.創(chuàng)建一個接收get請求的服務(wù)器

// 1.get.js
const express = require("express");
const app = express();

const port = 8080;

// path 是服務(wù)器上的路徑,callback是當(dāng)路由匹配時要執(zhí)行的函數(shù)。
app.get("/", function(req,res){ // 注冊一個get請求路由
    res.end("hello express!"); // 結(jié)束響應(yīng)
});

app.listen(port,function(){ // 開啟監(jiān)聽端口
    console.log(`server started on port ${port}`);
});

2.執(zhí)行上面代碼

nodemon是一種實用工具,將為您的源的任何變化并自動重啟服務(wù)器監(jiān)控。

$ nodemon 1.get.js

瀏覽器訪問 localhost:8080 結(jié)果如下:
hello express!

若訪問一個未處理的路由路徑 如localhost:8080/user 結(jié)果如下:
Cannot GET /user  也就是我們常見的404 Not Found
根據(jù)上面測試案例自己實現(xiàn)一個express 源碼分析

注冊一個簡單路由

app.get("/get", function(req, res) {
    res.send("hello express");
});

express源碼目錄結(jié)構(gòu)

路由系統(tǒng)

對于路由中間件,在整個個Router路由系統(tǒng)中stack 存放著一個個layer, 通過layer.route 指向route路由對象, route的stack的里存放的也是一個個layer,每個layer中包含(method/handler)。

在源碼里面主要涉及到幾個類和方法

createApplicaton
Application(proto)
Router
Route
Layer

express()返回一個app

實際上express指向內(nèi)部createApplication函數(shù) 函數(shù)執(zhí)行返回app

var mixin = require("merge-descriptors"); // merge-descriptors是第三方模塊,合并對象的描述符
var proto = require("./application"); // application.js中 導(dǎo)出 proto

function createApplicaton() { // express()
    var app = function(req, res, next) { // app是一個函數(shù)
        app.handle(req, res, next); // 處理路由
    };

    mixin(app, EventEmitter.prototype, false); // 將EventEmitter.prototype的對象屬性合并到app上一份
    mixin(app, proto, false); // 將proto中的掛載的一些屬性方法 如 app.get app.post 合并到app上
    app.init(); // 初始化 聲明一些私有屬性
    return app;
}

app上的很多屬性方法都來自application.js導(dǎo)出的proto對象, 在router/index.js中 也有一個命名為proto的函數(shù)對象 掛載了一些靜態(tài)屬性方法 proto.handle proto.param proto.use ...

// router/index.js
var proto = module.exports = function(options) {}

// 在application.js 導(dǎo)出的proto中 掛載這合并到app上的請求方法,源碼如下:

// application.js

// methods是一個數(shù)組集合,里面存放了一系列http請求方法 通過遍歷給app上過載一系列請求方法,即app.get()、app.post() 等
methods.forEach(function(method){
  app[method] = function(path){
    if (method === "get" && arguments.length === 1) {
      // app.get(setting)
      return this.set(path);
    }

    this.lazyrouter(); // 創(chuàng)建一個Router實例 this._router = new Router();

    var route = this._router.route(path); // 對應(yīng)router/index 中的 proto.route
    route[method].apply(route, slice.call(arguments, 1)); // 添加路由中間件 下面詳細(xì)講解
    return this;
  };
});

this.lazyrouter 用來創(chuàng)建一個Router實例 并掛載到 app._router

// application.js
methods.forEach(function(method){
  app[method] = function(path){

    this.lazyrouter(); // 創(chuàng)建一個Router實例 this._router = new Router();
  }
});

app.lazyrouter = function lazyrouter() {
  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.use用來添加非路由中間件,app[method]添加路由中間件,這兩種添加方式都在內(nèi)部調(diào)用了Router的相關(guān)方法來實現(xiàn):

注冊非路由中間件
// application.js

app.use = function(fn) { // fn 中間件函數(shù) 有可能是[fn]
    var offset = 0;
    var path = "/";

    var fns = flatten(slice.call(arguments, offset)); // 展平數(shù)組
    // setup router
    this.lazyrouter(); // 創(chuàng)建路由對象
    var router = this._router;

    fns.forEach(function(fn) {
      router.use(path, function mounted_app(req, res, next) { // app.use 底層調(diào)用了router.use
      var orig = req.app;
      fn.handle(req, res, function (err) {
        setPrototypeOf(req, orig.request)
        setPrototypeOf(res, orig.response)
        next(err);
      });
    });
}

通過上面知道了 app.use底層調(diào)用了router.use 接下來我們再來看看router.use

// router/index.js
var Layer = require("./layer");

proto.use = function(fn) {
    var offset = 0; // 參數(shù)偏移值
    var path = "/"; // 默認(rèn)路徑 /  因為 use

    // 第一個參數(shù)有可能是path 所以要從第二個參數(shù)開始截取
    if (typeof arg !== "function") {
         offset = 1;
         path = fn;
    }

    // 展平中間件函數(shù)集合 如中間件函數(shù)是以數(shù)組形式注冊的如[fn, fn] 當(dāng)slice截取時會變成[[fn, fn]] 所以需要展平為一維數(shù)組
    var callbacks = flatten(slice.call(arguments, offset)); // 截取中間件函數(shù)

    for(var i = 0; i < callbacks.length; i++) { // 遍歷每一個中間件函數(shù)
        var fn = callbacks[i];
        if (typeof fn !== "function") { // 錯誤提示
          throw new TypeError("Router.use() requires a middleware function but got a " + gettype(fn))
        }

        // 添加中間件
        // 實例化一個layer 對象
        var layer = new Layer(path, {
            sensitive: this.caseSensitive,
            strict: false,
            end: false
        }, fn);

        // 非路由中間件,該字段賦值為undefined
        layer.route = undefined;
        this.stack.push(layer); // 將layer添加到 router.stack
    }
}
注冊路由中間件

在上面application.js 中的app對象 添加了很多http關(guān)于請求 app[method]就是用來注冊路由中間件

// application.js

var app = exports = module.exports = {};

var Router = require("./router");
var methods = require("methods");

methods.forEach(function(method){
  app[method] = function(path){ // app上添加 app.get app.post app.put 等請求方法

    if (method === "get" && arguments.length === 1) {
      // app.get(setting)
      return this.set(path);
    }

    this.lazyrouter(); // 創(chuàng)建Router實例對象 并賦給this._router

    // 調(diào)用this._router.route => router/index.js中的proto.route
    var route = this._router.route(path);
    route[method].apply(route, slice.call(arguments, 1)); // 調(diào)用router中對應(yīng)的method方法 注冊路由
    return this;
  };
});

在上面 app[method]中底層實際調(diào)用了router[method] 也就是 this._router.route[method]

我們看看this._router.route(path); 這段代碼發(fā)生了什么

var route = this._router.route(path); // 里面創(chuàng)建了一個route實例 并返回
route[method].apply(route, slice.call(arguments, 1)); // 調(diào)用route中的對象的route[method]

router 中的 this._router.route 創(chuàng)建一個route對象 生成一個layer 將path 和 route.dispatch 傳入layer, layer的route指向 route對象 將Router和Route關(guān)聯(lián)來起來, 最后把route對象作為返回值

// router/index.js

var Route = require("./route");
var Layer = require("./layer");

proto.route = function (path) {
    var route = new Route(path); // app[method]注冊一個路由 就會創(chuàng)建一個route對象

    var layer = new Layer(path, { // 生成一個route layer
      sensitive: this.caseSensitive,
      strict: this.strict,
      end: true
    }, route.dispatch.bind(route)); // 里將生成的route對象的dispatch作為參數(shù)傳給layer里面

    // 指向剛實例化的路由對象(非常重要),通過該字段將Router和Route關(guān)聯(lián)來起來
    layer.route = route;

    this.stack.push(layer); // 將layer添加到Router的stack中
    return route; // 將生成的route對象返回
}

對于路由中間件,路由容器中的stack(Router.stack)里面的layer通過route字段指向了路由對象,那么這樣一來,Router.stack就和Route.stack發(fā)生了關(guān)聯(lián),關(guān)聯(lián)后的示意模型如下圖所示:

app[method]中 調(diào)用 route[method].apply(route, slice.call(arguments, 1));

// router/index.js

// 實際上 application.js 中 app[method] 調(diào)用的是 router對象中對應(yīng)的http請求方法 額外添加了一個all方法
methods.concat("all").forEach(function(method){
  proto[method] = function(path){
    var route = this.route(path) // 返回創(chuàng)建的route對象
    route[method].apply(route, slice.call(arguments, 1)); // router[method] 又調(diào)用了 route[method]
    return this;
  };
});

最后我們來看下 router/route.js 中的Route

// router/route.js

function Route(path) { // Route類
  this.path = path;
  this.stack = []; // route的stack

  debug("new %o", path)
  this.methods = {}; // 用于各種HTTP方法的路由處理程序
}

var Layer = require("./layer");
var methods = require("methods");

// 又是相同的一段代碼 在給Route的原型上添加http方法
methods.forEach(function(method){
  Route.prototype[method] = function(){
    var handles = flatten(slice.call(arguments)); // 傳遞進來的處理函數(shù)

    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 a callback function but got a " + type
        throw new Error(msg);
      }

      debug("%s %o", method, this.path)

      var layer = Layer("/", {}, handle); // 在route中也有l(wèi)ayer 里面保存著 method和handle
      layer.method = method;

      this.methods[method] = true; // 標(biāo)識 存在這個method的路由處理函數(shù)
      this.stack.push(layer); // 將layer 添加到route的stack中
    }

    return this; // 將route對象返回
  };
});

Route 中的all方法

Route.prototype.all = function all() {
  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.all() requires a callback function but got a " + type
      throw new TypeError(msg);
    }

    var layer = Layer("/", {}, handle);
    layer.method = undefined; // all 匹配所以方法

    this.methods._all = true; // all方法標(biāo)識
    this.stack.push(layer); // 添加到route的stack中
  }

  return this;
};

最終路由注冊關(guān)系鏈 app[method] => router[method] => route[method] 最終在route[method]里完成路由注冊

接下來我們看看Layer
// route/layer.js
var pathRegexp = require("path-to-regexp");

module.exports = Layer;

function Layer(path, options, fn) {
    if (!(this instanceof Layer)) {
        return new Layer(path, options, fn);
    }

    this.handle = fn; // 存儲處理函數(shù)
    this.regexp = pathRegexp(path, this.keys = [], opts); // 根據(jù)路由路徑 生成路由規(guī)則正則 用來路由匹配
}

Layer.prototype.match = function(path) { // 請求路徑 是否匹配 該層路由路徑規(guī)則this.regexp

}
啟動server
// application.js
var http = require("http");

app.listen = function listen() {
  var server = http.createServer(this); // this => express.js 中的 app
  return server.listen.apply(server, arguments);
};

// express.js 中的 app
var app = function(req, res, next) {
  app.handle(req, res, next);
};
路由調(diào)用

app.handle 調(diào)用this._router.handle 進行路由處理

express.js
function createApplicaton() {
    let app = function(req, res, next) { // 持續(xù)監(jiān)聽請求
        app.handle(req, res, next); // 路由處理函數(shù)
    }
}

express.js中的app.handle 實際來自application.js的app.handle 底層調(diào)用router.handle

// application.js

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

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

  router.handle(req, res, done); // 底層調(diào)用router.handle
};

router.handle 調(diào)用 內(nèi)部next函數(shù) 在router的stack中尋找匹配的layer

// router/index.js


function matchLayer(layer, path) {
  try {
    return layer.match(path);
  } catch (err) {
    return err;
  }
}


proto.handle = function(req, res, done) {
    // middleware and routes
    var stack = self.stack; // router的stack 里面存放著中間件和路由
    var idx = 0;

    next();
    function next(err) {

        if (idx >= stack.length) { // 邊界判斷
             setImmediate(done, layerError);
             return;
        }

        // 獲取請求路徑
        var path = getPathname(req);

        var layer;
        var match;
        var route;
        while (match !== true && idx < stack.length) { // 一層層進行路由匹配
          layer = stack[idx++]; // 從router的stack取出沒一個layer

          match = matchLayer(layer, path); // matchLayer調(diào)用 該layer的match方法進行路由路徑進行匹配

          route = layer.route; // 得到關(guān)聯(lián)的route對象
          var method = req.method; // 獲取請求方法

          var has_method = route._handles_method(method); // 調(diào)用route的_handles_method返回Boolean值

          if (match !== true) { // 如果沒匹配上處理
            return done(layerError);
          }

          if (route) { // 調(diào)用layer的handle_request 處理請求 執(zhí)行handle
             return layer.handle_request(req, res, next);
          }
        }
    }
}

路由處理 app.handle => router.handle => layer.handle_request

layer的handle_request 調(diào)用next一次獲取route stack中的處理方法

Layer.prototype.handle_request = function handle(req, res, next) {
  var fn = this.handle;  // 獲取new Layer時保存的handle
    // function Layer() {
        // this.handle = fn;
    // }

  if (fn.length > 3) {
    // not a standard request handler
    return next();
  }

  try {
    fn(req, res, next);
  } catch (err) {
    next(err);
  }
};
小結(jié)

app.use用來添加非路由中間件,app[method]添加路由中間件,中間件的添加需要借助Router和Route來完成,app相當(dāng)于是facade,對添加細(xì)節(jié)進行了包裝。

Router可以看做是一個存放了中間件的容器。對于里面存放的路由中間件,Router.stack中的layer有個route屬性指向了對應(yīng)的路由對象,從而將Router.stack與Route.stack關(guān)聯(lián)起來,可以通過Router遍歷到路由對象的各個處理程序。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/93665.html

相關(guān)文章

  • 學(xué)習(xí)express.js源代碼的方法

    摘要:學(xué)習(xí)的源代碼的好處自然不少。閱讀源代碼可以幫你實現(xiàn)你的好奇心。本文會推薦一些的源代碼分析文章,可以幫助更快的,更加全方位的理解研讀之。 盡管有Hapi,Koa等有力的競爭者,express.js依然是非常流行的nodejs web服務(wù)器框架,畢竟它早于2007年就已經(jīng)在開發(fā)了。 學(xué)習(xí)expressjs的源代碼的好處自然不少。 它可以幫你深刻理解HTTP協(xié)議,這個協(xié)議是做前端后端都必然需...

    huaixiaoz 評論0 收藏0
  • 關(guān)于Vue2一些值得推薦的文章 -- 五、六月份

    摘要:五六月份推薦集合查看最新的請點擊集前端最近很火的框架資源定時更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風(fēng)荷舉。家住吳門,久作長安旅。五月漁郎相憶否。小楫輕舟,夢入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請::點擊::集web前端最近很火的vue2框架資源;定時更新,歡迎 Star 一下。 蘇...

    sutaking 評論0 收藏0
  • 關(guān)于Vue2一些值得推薦的文章 -- 五、六月份

    摘要:五六月份推薦集合查看最新的請點擊集前端最近很火的框架資源定時更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風(fēng)荷舉。家住吳門,久作長安旅。五月漁郎相憶否。小楫輕舟,夢入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請::點擊::集web前端最近很火的vue2框架資源;定時更新,歡迎 Star 一下。 蘇...

    khs1994 評論0 收藏0
  • 基于 Backbone + node 的個人簡歷生成器(個人學(xué)習(xí)總結(jié))

    摘要:應(yīng)用的功能這個應(yīng)用是一個個人簡歷生成器。比較好的教程有這一個。這樣的命名污染問題自然顯而易見。而且發(fā)出多次請求也會影響性能。明顯不利于維護。然而我希望能夠不發(fā)生變化,因為是在文件的前提下的標(biāo)簽頁,不能換一個標(biāo)簽就重建一個。 為什么學(xué)習(xí)backbone?這是個好問題。在這個前端框架爆炸的年代,比起backbone,對開發(fā)來說有更多更好的選擇,react,vue,angular等等。但這些...

    lansheng228 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<