.4-淺析express原始碼之applicaiton模組(3)-compile函式

書生小龍發表於2018-04-17

  基本上application模組的api都看的差不多了,但是在app.set中還有一個遺漏點,如下:

app.set = function set(setting, val) {
    // ...設值

    // 觸發特殊compile函式
    switch (setting) {
        case `etag`:
            this.set(`etag fn`, compileETag(val));
            break;
        case `query parser`:
            this.set(`query parser fn`, compileQueryParser(val));
            break;
        case `trust proxy`:
            this.set(`trust proxy fn`, compileTrust(val));

            Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
                configurable: true,
                value: false
            });

            break;
    }

    return this;
};

  在對etag、query parser、trust proxy屬性進行設定時,會根據值設定對應的fn屬性。

  這幾個值都比較特殊,在官網有對option進行解釋,下面逐個講解。

 

etag

  首先來看etag,概念可參考wiki:https://en.wikipedia.org/wiki/HTTP_ETag,簡單描述一下該欄位。

1、每一次資源內容變更會生成一個新的ETag。

2、標準中未對ETag的格式做規定,常規情況下可使用時間戳的加密字串。

3、etag分為weak、strong兩種模式,區別在於weak的頭部有個W/,強模式要求內容是位元組級別的相等,總之非常嚴格。

4、客戶端使用post類請求搭配If-Match頭部可防止由伺服器資源更新導致的併發錯誤,出錯會返回狀態碼412(先決條件出錯)。

5、客戶端使用get類請求搭配RANGE欄位可保證返回同樣的範圍資源,出錯會返回狀態碼416(資源範圍不匹配)。

6、伺服器可根據客戶端傳送的If-None-Match欄位跟伺服器上資源的ETag做對比,若匹配則返回304狀態碼,表示資源仍然可用。

 

  可選的設值有三種:

1、布林值

  true代表使用weak ETag,預設值。

  false代表關閉ETag選項。

2、字串

  `weak`、`strong`分別代表使用weak ETag、strong ETag。

3、函式

  自定義ETag生成函式。

  compileETag的函式原始碼如下:

exports.compileETag = function(val) {
    var fn;

    if (typeof val === `function`) {
        return val;
    }
    /**
     * true/weak => weak ETag
     * strong => strong ETag
     * false => 禁ETag
     */
    switch (val) {
        case true:
            fn = exports.wetag;
            break;
        case false:
            break;
        case `strong`:
            fn = exports.etag;
            break;
        case `weak`:
            fn = exports.wetag;
            break;
        default:
            throw new TypeError(`unknown value for etag function: ` + val);
    }

    return fn;
}

  如果傳入不合法的值會報錯, 函式比較簡單,就不分析了,輸出的etag、wetag也很簡單,如下:

exports.etag = createETagGenerator({ weak: false })
exports.wetag = createETagGenerator({ weak: true })
function createETagGenerator(options) {
    // 返回一個函式
    return function generateETag(body, encoding) {
        // 返回一個Buffer
        var buf = !Buffer.isBuffer(body) ?
            Buffer.from(body, encoding) :
            body;
        // 傳入Buffer與weak選項
        return etag(buf, options)
    }
}

  這個body暫時還不知道是什麼東西,會將其轉化為一個Buffer。

  etag函式來源於引入的工具模組,原始碼如下所示:

function etag(entity, options) {
    if (entity == null) {
        throw new TypeError(`argument entity is required`)
    }

    // 判斷例項是否是stat物件
    var isStats = isstats(entity);
    // weak變數來源於options或者例項型別
    var weak = options && typeof options.weak === `boolean` ?
        options.weak :
        isStats;

    // validate argument
    if (!isStats && typeof entity !== `string` && !Buffer.isBuffer(entity)) {
        throw new TypeError(`argument entity must be string, Buffer, or fs.Stats`)
    }

    // 生成ETag
    var tag = isStats ?
        stattag(entity) :
        entitytag(entity);
    // 根據weak屬性返回對應型別的ETag
    return weak ?
        `W/` + tag :
        tag;
}

  函式分為四步:

1、判斷例項型別

2、指定weak的值

3、生成ETag

4、根據weak的值生成完整的ETag

  首先是stat型別判斷,原始碼如下:

function isstats(obj) {
    // 用instanceof直接判斷
    if (typeof Stats === `function` && obj instanceof Stats) {
        return true
    }

    // 鴨子型別
    return obj && typeof obj === `object` &&
        `ctime` in obj && toString.call(obj.ctime) === `[object Date]` &&
        `mtime` in obj && toString.call(obj.mtime) === `[object Date]` &&
        `ino` in obj && typeof obj.ino === `number` &&
        `size` in obj && typeof obj.size === `number`
}

  鴨子型別的判斷其實並不完整,一個Stat物件的值很多,參考連結:http://nodejs.cn/api/fs.html#fs_class_fs_stats

  不過對於生成ETag來說,這幾個估計就夠了。

  第二步根據options或者型別指定weak的值。

  第三步根據型別生成對應的ETag,原始碼如下:

function stattag(stat) {
    // 把兩個屬性轉換為16進位制字串
    var mtime = stat.mtime.getTime().toString(16);
    var size = stat.size.toString(16);
    // 拼接
    return `"` + size + `-` + mtime + `"`
}
function entitytag(entity) {
    if (entity.length === 0) {
        // 直接返回加密後的空字串 這是個常量
        return `"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"`
    }

    // 加密處理
    var hash = crypto
        .createHash(`sha1`)
        .update(entity, `utf8`)
        .digest(`base64`)
        .substring(0, 27);

    // 計算內容長度
    var len = typeof entity === `string` ?
        Buffer.byteLength(entity, `utf8`) :
        entity.length;
    // 拼接長度與hash值
    return `"` + len.toString(16) + `-` + hash + `"`;
}

  從這裡可以發現,生成的ETag並沒有特殊的格式要求,唯一的要求就是不重複。

  第四步,就是根據weak屬性在ETag前面加個W/了。

  至此,ETag部分完結。

 

query parser

  這個超簡單,直接看原始碼:

exports.compileQueryParser = function compileQueryParser(val) {
    var fn;

    if (typeof val === `function`) {
        return val;
    }
    /**
     * true/simple => 內建querystring模組
     * extended => qs模組
     * false => 不解析
     */
    switch (val) {
        case true:
            fn = querystring.parse;
            break;
        case false:
            fn = newObject;
            break;
        case `extended`:
            fn = parseExtendedQueryString;
            break;
        case `simple`:
            fn = querystring.parse;
            break;
        default:
            throw new TypeError(`unknown value for query parser function: ` + val);
    }

    return fn;
}

  這個屬性是指定引數解析方式的,可選的值也有2種:

1、布林值

  true與simple一樣,使用node內建的querystring模組的parse方法。false則代表不進行parse,返回空物件。

2、字串

  simple略。extended代表使用qs模組的parse方法解析,程式碼如下:

// var qs = require(`qs`);
function parseExtendedQueryString(str) {
    return qs.parse(str, {
        // 該選項允許解析後物件的鍵覆蓋原型方法
        allowPrototypes: true
    });
}

  這兩種方法都可以用來解析URL的引數,整體來看區別如下:

1、內建的querystring模組api比較簡單(一般情況我覺得都夠用了),並且返回的物件並不繼承於Object,所以原型方法均無法使用。

2、qs模組的方法非常多,解析各種奇怪的字串,返回的物件為Object型別(也可指定返回Object.create(null)型別)。

 

trust proxy

  這個屬性有中文的解釋,可以去看一下:http://www.expressjs.com.cn/guide/behind-proxies.html

  原始碼過了一下,沒有什麼意思,而且也不是很懂,所以暫時跳過啦。。。

 

  這個模組算是完結了。

相關文章