.5-淺析express源碼之Router模塊(1)-lazyrouter

来源:https://www.cnblogs.com/QH-Jimmy/archive/2018/04/17/8863429.html
-Advertisement-
Play Games

模塊application已經完結,開始講Router路由部分。 切入口仍然在application模塊中,方法就是那個隨處可見的lazyrouter。 基本上除了初始化init方法,其餘的app.use、app.route、app.param等等,所有涉及到路由的方法都會調用一次這個函數,用來初始 ...


  模塊application已經完結,開始講Router路由部分。

  切入口仍然在application模塊中,方法就是那個隨處可見的lazyrouter。

  基本上除了初始化init方法,其餘的app.use、app.route、app.param等等,所有涉及到路由的方法都會調用一次這個函數,用來初始化一個應用的內部路由。

  而這個內部路由對於每個應用來說是唯一的,可以看下源碼:

app.lazyrouter = function lazyrouter() {
    if (!this._router) {
        // 生成一個實例
        this._router = new Router({
            caseSensitive: this.enabled('case sensitive routing'),
            strict: this.enabled('strict routing')
        });
        // params解析中間件調用
        this._router.use(query(this.get('query parser fn')));
        // express框架自定義的內部中間件
        this._router.use(middleware.init(this));
    }
};

  很清晰的步驟,在生成一個Router實例後,調用了兩個中間件。

  這裡有一個問題,為什麼不在初始化的函數中直接生成一個預設路由呢?

  原因在於設置路由的相關參數需要調用app.set方法,這個方法明顯需要有app實例,如果在獲取app實例的時候就初始化了一個路由,這個路由的參數就沒辦法配置了。因此,在獲取app實例後,必須先對路由參數進行配置,然後再調用對應的app.use等方法。

  簡單看一眼構造函數:

var proto = module.exports = function(options) {
    var opts = options || {};
    // 跟app一樣的函數
    function router(req, res, next) {
        router.handle(req, res, next);
    }
    // 原型方法掛載
    setPrototypeOf(router, proto)

    router.params = {};
    router._params = [];
    /**
     * caseSensitive => 區分大小寫 /foo vs /Foo
     * mergeParmas => 保留父路由參數
     * strict => 嚴格模式 /foo vs /foo/
     */
    router.caseSensitive = opts.caseSensitive;
    router.mergeParams = opts.mergeParams;
    router.strict = opts.strict;
    router.stack = [];

    return router;
};

  預設情況下,三個參數的值均為undefined,構造函數沒有任何初始化的操作,直接返回了router函數。

  接下來是兩個中間件。

 

query

  先把那行代碼單獨貼出來:

// var query = require('./middleware/query');
this._router.use(query(this.get('query parser fn')));

  前面有講解3個特殊鍵的set會觸發對應的compile方法設置fn,這裡的query parser fn就是之一。

  預設情況下,query parser值為extended,對應的query parser fn為qs.parse方法,因此這裡query方法的參數為一個函數。

  看一眼query方法:

module.exports = function query(options) {
    // options為函數 merge後opts也是函數
    var opts = merge({}, options)
    var queryparse = qs.parse;
    // 參數修正
    if (typeof options === 'function') {
        queryparse = options;
        opts = undefined;
    }
    // 相容 設置配置參數
    if (opts !== undefined && opts.allowPrototypes === undefined) opts.allowPrototypes = true;
    // 中間件標準結構
    return function query(req, res, next) {
        if (!req.query) {
            var val = parseUrl(req).query;
            req.query = queryparse(val, opts);
        }

        next();
    };
};

  這裡的形參options既可以是配置參數,也可以是預設的解析方法。

  如果將query parser設為false,這裡的options就是一個空對象,express還是會指定一個parser,即源碼中的qs.parse。搞了半天,設置false或者extended都是預設的qs.parse。

  在確實了對應的parse方法與參數後,就開始進行url解析,先處理url,獲取query參數,再解析query設置到req對象上。

 

parseUrl

  講這個之前,需要稍微理解下nodejs的url模塊,特別是Url與URL。

  這兩東西在網上沒查到詳細的區別,通過試API,發現差別還挺大:

1、Url為遺留API,構造函數不接受參數,通過無參構造後,可以調用parse方法解析一個url路徑來獲得一個實例,實例屬性包含protocol、auth等一系列東西。

2、URL為WHATWG API,推薦使用的新API,可以直接通過new操作傳一個url進去獲得實例,屬性同樣包含那些,但是在鍵名與分類略有區別。

  詳細情況可見:http://nodejs.cn/api/url.html#url_url_strings_and_url_objects。

 

  雖然URL是新東西而且node推薦使用,但是在express源碼的這個方法中依然使用的是老Url,入口函數如下:

function parseurl(req) {
    // 這個屬性是原生的
    var url = req.url;

    if (url === undefined) return undefined;
    // 嘗試獲取緩存屬性
    var parsed = req._parsedUrl;
    // 判斷有沒有緩存
    if (fresh(url, parsed)) return parsed;

    // 解析url
    parsed = fastparse(url);
    parsed._raw = url;
    // 添加緩存並返回結果
    return (req._parsedUrl = parsed)
};

  所有的解析都基於一個原生的屬性,即req.url,該屬性返回請求的原始URL。

  這裡的獲取緩存就不看了,比較簡單,直接看如何快速解析url路徑:

function fastparse(str) {
    // 當路徑結構為純path(例如:/path/ext?a=1)時,直接調用node原生的parse方法
    if (typeof str !== 'string' || str.charCodeAt(0) !== 0x2f /* / */ ) {
        return parse(str)
    }

    var pathname = str
    var query = null
    var search = null

    // This takes the regexp from https://github.com/joyent/node/pull/7878
    // 這個issue主要講當url是純路徑時 用node原生的Url.parse會更快
    for (var i = 1; i < str.length; i++) {
        switch (str.charCodeAt(i)) {
            /**
             * 遇到問號開始切割路徑
             * http://www.baidu.com?a=1 => 
             * {
             *      pathname: http://www.baidu.com,
             *      query: a=1,
             *      search: ?a=1,
             * }
             */
            case 0x3f:
                /* ?  */
                if (search === null) {
                    pathname = str.substring(0, i)
                    query = str.substring(i + 1)
                    search = str.substring(i)
                }
                break
                // 遇到其餘不合理的情況調用原生方法
            case 0x09:
                /* \t */
            case 0x0a:
                /* \n */
            case 0x0c:
                /* \f */
            case 0x0d:
                /* \r */
            case 0x20:
                /*    */
            case 0x23:
                /* #  */
            case 0xa0:
            case 0xfeff:
                return parse(str)
        }
    }
    // 生成一個Url對象或者空對象
    var url = Url !== undefined ?
        new Url() : {};
    // 添加對應的屬性
    url.path = str
    url.href = str
    url.pathname = pathname
    url.query = query
    url.search = search

    return url
}

  看似很長,實則很簡單。簡單來說,就是根據問號來切割url,特殊情況就全部扔給內置模塊解析,最後返回url對象。

  在獲取到對應的url尾部參數後,調用parser方法解析生成一個參數對象掛載到req上,所以在實際應用中,我們可以直接調用req.query來得到請求參數值。

 

middleware.init

  這個中間件是express自定義的,也不知道叫什麼,所以直接用調用名作為小標題了。

  源碼如下:

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;
        // 本地模塊原型設置
        setPrototypeOf(req, app.request)
        setPrototypeOf(res, app.response)

        res.locals = res.locals || Object.create(null);

        next();
    };
};

  這個中間件的主要作用就是把內置模塊的屬性、方法全部加到原生的req、res上面去,後面就能使用express的方法了。

 

  解析完畢。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • n CSS簡介 CSS:Cascading Style Sheets 層疊樣式表 CSS的主要目的:是給HTML標記添加各種各樣的表現(格式、樣式)。比如:文字樣式、背景、文本樣式、鏈接樣式。 提示:CSS是給HTML標記加的樣式:JS是給HTML標記加的行為。HTML標記是最先出現的。 HTML超 ...
  • 零、前言 錯過了今年雙十一、雙十二噹噹圖書網的半價優惠,挺遺憾,但不買書心裡又癢癢的,後來在12月13號晚上以79折買了一些JavaScript的經典書籍:《JavaScript DOM編程藝術(第2版)》、《 JavaScript高級程式設計(第3版)》、《鋒利的jQuery(第2版)》和幾本其他 ...
  • vue中迴圈遍歷使用的指令是v-for 1.v-for遍曆數組 (1)value in arr 遍曆數組中的元素 (2)(value,index) in arr 遍曆數組中的元素和數組下標 運行代碼: 輸出結果: 2.v-for遍歷json對象 (1)value in json 遍歷json對象中的 ...
  • 新浪佈局 初始佈局代碼: .header{height:;background:;} JavaScript焦點事件 docment.focus(); JS事件冒泡機制:當一個元素接收到事件的時候,會把它接收到的所有傳播給它的父級,一直到頂層Window.事件。 阻止冒泡 :當前要阻止冒泡的事件函數中 ...
  • 我的代碼是這麼寫的 window.onload=function(){ function sendRequest(){ var script=document.getElementById("script"); //360介面問題 script.src="http://tq.360.cn/api/w ...
  • 屬性繼承:可以與不可以的元素有哪些: https://www.cnblogs.com/thislbq/p/5882105.html 可以繼承的 1文本相關屬性 2列表相關屬性3color屬性 任何一條與css繼承值衝突的屬性值都會覆蓋繼承的屬性值 盒子模型:position四種屬性值 display ...
  • Chrome瀏覽器目前是網路上可用的最好的瀏覽器之一,並且自2011年11月超越了Firefox瀏覽器之後,已經成為了互聯網上占主導地位的瀏覽器。 本篇文章將與大家分享一些與前端開發有關的實用的Chrome瀏覽器插件,這些插件可以幫助你提高前端開發工作效率。 Allow Control Allow ...
  • 設置Cookie過期的兩種方式: $.cookie('名', '值', { expires: 過期時間(DateTime), path: "/", domain: window.DomainName }); $.cookie('名', '值', { expires: 過期天數(Int), path... ...
一周排行
    -Advertisement-
    Play Games
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...