.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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...