.4-浅析express源码之applicaiton模块(3)-compile函数
基本上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
源码过了一下,没有什么意思,而且也不是很懂,所以暂时跳过啦。。。
这个模块算是完结了。