基本上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
原始碼過了一下,沒有什麼意思,而且也不是很懂,所以暫時跳過啦。。。
這個模組算是完結了。