原文地址 :阅读原文
API为:etag(entity, [options])
这个方法为给定的实体产生一个strong etag。这个方法必须获取这个实体的完整内容。可以是Buffer,可以是fs.Stats。默认情况下,这个strong etag的产生是不需要fs.Stats的,因为这个fs.Stats会产生weak Etag而不是strong etag,不过这种默认行为可以通过options.weak来覆盖!options:
weak:指定产生的Etag是否会包含弱验证符号(即开头的W/),如果是true那么会包含字符串"W/"。默认是false,除非我们传入的实体是fs.Stats,这时候weak就是true了表示有开头的弱验证符号,而且是通过文件的内容来产生的etag值!
问题1:etag的API中第一个参数是不是必须的?
//必须含有参数
if (entity == null) {
throw new TypeError('argument entity is required')
}
问题2:第一个参数必须是Buffer,fs.Stats或者String?
// support fs.Stats object
var isStats = isstats(entity)
//判断是否是文件描述符
var weak = options && typeof options.weak === 'boolean'
? options.weak
: isStats
//如果用户传入了weak,那么用用户的weak为主,否则weak保存的就是是否是文件描述符
// validate argument
if (!isStats && typeof entity !== 'string' && !Buffer.isBuffer(entity)) {
throw new TypeError('argument entity must be string, Buffer, or fs.Stats')
}
很显然,上面的问题的答案yes。那么他是如何判断一个对象是否为文件描述符呢?
function isstats(obj) {
// genuine fs.Stats
//判断是否是原生的文件描述符,通过instanceof判断
if (typeof Stats === 'function' && obj instanceof Stats) {
return true
}
// quack quack
//n. 庸医;鸭叫声 adj. 骗人的;冒牌医生 vi. (鸭子)嘎嘎叫;吹嘘;大声闲聊
//如果不是原生的文件描述符,那么判断这个对象是否有ctime,同时ctime(change time)是Date对象,是否有mtime,同时mtime(modified time)是Date
//是否有ino,而且是number,是否有size同时是number!
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'
}
很显然是通过instanceof或者判断一个对象的ctime,mtime,ino和size属性来完成的!
问题3:如果是文件描述符和不是文件描述符有什么区别?
var tag = isStats
? stattag(entity)
: entitytag(entity)
//如果是文件描述符那么用stattag来产生tag,否则用entitytag
return weak
? 'W/' + tag
: tag
如果是文件描述符那么是通过stattag来完成,否则用entitytag来完成的
//如果传入了文件描述符那么获取他的mtime(modified time)和size来产生etag,很显然这时候的etag产生不需要文件的内容
//而只是用文件描述符的mtime和size!
function stattag(stat) {
var mtime = stat.mtime.getTime().toString(16)
var size = stat.size.toString(16)
return '"' + size + '-' + mtime + '"'
}
也就是说如果是文件描述符那么
只是用的mtime,size来产生etag!
/**
* Generate an entity tag.
* @param {Buffer|string} entity
* @return {string}
* @private
*/
function entitytag(entity) {
if (entity.length === 0) {
// fast-path empty
//如果传入的内容为空那么返回的etag为固定值
return '"0-1B2M2Y8AsgTpgAmY7PhCfg"'
}
// compute hash of entity
var hash = crypto
.createHash('md5')
.update(entity, 'utf8')
.digest('base64')
.replace(base64PadCharRegExp, '')
//用文件的内容来产生etag,同时取消其中连续的=号!
// compute length of entity
var len = typeof entity === 'string'
? Buffer.byteLength(entity, 'utf8')
: entity.length
//如果传入的参数是string那么获取这个string的字节长度,如果不是string那么直接获取他的length属性
return '"' + len.toString(16) + '-' + hash + '"'
//所以返回的值为其长度的16进制编码,同时加上内容的hash值!
}
如果不是文件描述符那么这时候是通过"
内容的长度的十六进制+文件内容的md5的16进制来完成的"!
问题4:那么是不是如果是文件描述符就返回mtime+size来返回的etag,否则就返回内容长度的十六进制+文件内容的md5的16进制?
<pre name="code" class="javascript"> var weak = options && typeof options.weak === 'boolean'
? options.weak
: isStats
return weak
? 'W/' + tag
: tag
很显然会有如下的结论:
问题5:为什么要看这部分的原码,为什么要仔细学习etag部分?
这是因为express中的setting中有一个选项就是etag,这个etag的产生就是通过这个包来完成的,我们看看里面他关于etag的配置有那些?
我们看到其中etag可以为布尔值表示是否开启etag,而strong表示开启strong Etag,反之表示weak,当然也可以指定一个函数,这个函数让自己控制对etag的生成!仔细阅读etag
这部分的原码如下:
'use strict'
/**
* Module exports.
* @public
*/
module.exports = etag
var crypto = require('crypto')
var Stats = require('fs').Stats
//引入node.js的crypto模块和fs模块
var base64PadCharRegExp = /=+$/
var toString = Object.prototype.toString
/**
* Generate an entity tag.
* @param {Buffer|string} entity
* @return {string}
* @private
*/
function entitytag(entity) {
if (entity.length === 0) {
// fast-path empty
//如果传入的内容为空那么返回的etag为固定值
return '"0-1B2M2Y8AsgTpgAmY7PhCfg"'
}
// compute hash of entity
var hash = crypto
.createHash('md5')
.update(entity, 'utf8')
.digest('base64')
.replace(base64PadCharRegExp, '')
//用文件的内容来产生etag,同时取消其中连续的=号!
// compute length of entity
var len = typeof entity === 'string'
? Buffer.byteLength(entity, 'utf8')
: entity.length
//如果传入的参数是string那么获取这个string的字节长度,如果不是string那么直接获取他的length属性
return '"' + len.toString(16) + '-' + hash + '"'
//所以返回的值为其长度的16进制编码,同时加上内容的hash值!
}
/**
* Create a simple ETag.
*
* @param {string|Buffer|Stats} entity
//第一个参数可以是String,Buffer,Stats
* @param {object} [options]
//第二个参数是一个对象,其中的weak属性是boolean
* @param {boolean} [options.weak]
//函数返回值为String
* @return {String}
* @public
*/
function etag(entity, options) {
//必须含有参数
if (entity == null) {
throw new TypeError('argument entity is required')
}
// support fs.Stats object
var isStats = isstats(entity)
//判断是否是文件描述符
var weak = options && typeof options.weak === 'boolean'
? options.weak
: isStats
//如果用户传入了weak,那么用用户的weak为主,否则weak保存的就是是否是文件描述符
// validate argument
if (!isStats && typeof entity !== 'string' && !Buffer.isBuffer(entity)) {
throw new TypeError('argument entity must be string, Buffer, or fs.Stats')
}
// generate entity tag
var tag = isStats
? stattag(entity)
: entitytag(entity)
//如果是文件描述符那么用stattag来产生tag,否则用entitytag
return weak
? 'W/' + tag
: tag
}
/**
* Determine if object is a Stats object.
*
* @param {object} obj
* @return {boolean}
* @api private
*/
function isstats(obj) {
// genuine fs.Stats
//判断是否是原生的文件描述符,通过instanceof判断
if (typeof Stats === 'function' && obj instanceof Stats) {
return true
}
// quack quack
//n. 庸医;鸭叫声 adj. 骗人的;冒牌医生 vi. (鸭子)嘎嘎叫;吹嘘;大声闲聊
//如果不是原生的文件描述符,那么判断这个对象是否有ctime,同时ctime(change time)是Date对象,是否有mtime,同时mtime(modified time)是Date
//是否有ino,而且是number,是否有size同时是number!
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'
}
/**
* Generate a tag for a stat.
*
* @param {object} stat
* @return {string}
* @private
*/
//如果传入了文件描述符那么获取他的mtime(modified time)和size来产生etag,很显然这时候的etag产生不需要文件的内容
//而只是用文件描述符的mtime和size!
function stattag(stat) {
var mtime = stat.mtime.getTime().toString(16)
var size = stat.size.toString(16)
return '"' + size + '-' + mtime + '"'
}