
从小到大都是优,你让我怎么从良
昨天解读了fresh这个库,我们了解了服务器是如何对比文件是否更新了,其中用到了etag,那么今天我们就趁热打铁,了解下etag是怎么生成的,同样是来自jshttp的ETag库。
ETag
ETag是干什么的,这里摘读下MDN的官方解释
如果给定URL中的资源更改,则一定要生成新的Etag值。 因此Etags类似于指纹,也可能被某些服务器用于跟踪。 比较etags能快速确定此资源是否变化,但也可能被跟踪服务器永久存留。
也就是说,资源发生改变的时候,etag的值一定发生了改变,那么etag又是如何生成的呢?内容变化,etag值就发生变化,那么就是说我们可以借助内容来生成etag。又到了,talk is cheap, show me the code 环节。
解读ETag
首先看入口,其中参数entity是要传进来的内容,options是可选值,那么entity如果是空值,肯定不行,直接抛出错误。
function etag (entity, options) {
if (entity == null) {
throw new TypeError('argument entity is required')
}
var isStats = isstats(entity)
...
}
entity的值除了是资源内容之外,也有可能是资源的状态对象,既包含文件创建时间、上次修改时间等一系列信息的对象,那么我们这时要先判断一下,因为使用stats对象生成Etag的策略跟使用资源内容的策略稍微不同。
function isstats (obj) {
// genuine fs.Stats
if (typeof Stats === 'function' && obj instanceof Stats) {
return true
}
// quack quack
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'
}
那么如何判断一个对象是否是stats呢?从上面我们可以看到,先看看它是不是fs.Stats的实例或者它有没有Stats对象该有的属性,满足其中一个,则说明entity是Stats对象。
接下来有个ETag的小知识点,不知道有没有人留意过,有些ETag是以W/开头的,啥意思呢,我们再来看看MDN的官方解释
'W/'(大小写敏感) 表示使用弱验证器。 弱验证器很容易生成,但不利于比较。 强验证器是比较的理想选择,但很难有效地生成。 相同资源的两个弱Etag值可能语义等同,但不是每个字节都相同。
W/开头的ETag是弱生成器。那么如果我们前面传进来的option.weak的值为true或者entity是Stats对象,那么将会使用弱验证器,因为Stats对象是有一定几率一样的,所以也属于弱验证器,但如果一个文件在秒级别经常被修改,但最后一次修改完的内容跟第一次一样,对于弱etag来说,可能etag不一样,但是强etag来说,值是一样的,
var weak = options && typeof options.weak === 'boolean'
? options.weak
: isStats
在这里,我们最后校验下传进来的参数,如果不是entity不是stats也不是string甚至也不是buffer,那直接抛出异常
// validate argument
if (!isStats && typeof entity !== 'string' && !Buffer.isBuffer(entity)) {
throw new TypeError('argument entity must be string, Buffer, or fs.Stats')
}
OK,那如果参数都没问题,我们就开始真正地生成Etag了
stat的entity使用stattag策略生成,其中stattag通过获取文件大小加上最近修改时间的16进制生成etag
而其他的使用entitytag生成策略,entity长度为0返回默认值,如果不为0,借助crypt去base64生成摘要且只截取前面27位,最后用文件大小和摘要生成etag。
我们可以看到,两种生成策略,唯一的区别是,一个是用mtime另一个是用内容摘要
// generate entity tag
var tag = isStats
? stattag(entity)
: entitytag(entity)
function stattag (stat) {
var mtime = stat.mtime.getTime().toString(16)
var size = stat.size.toString(16)
return '"' + size + '-' + mtime + '"'
}
function entitytag (entity) {
if (entity.length === 0) {
// fast-path empty
return '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"'
}
// compute hash of entity
var hash = crypto
.createHash('sha1')
.update(entity, 'utf8')
.digest('base64')
.substring(0, 27)
// compute length of entity
var len = typeof entity === 'string'
? Buffer.byteLength(entity, 'utf8')
: entity.length
return '"' + len.toString(16) + '-' + hash + '"'
}
最后一步,如果是弱etag,前面加上W/,打完收工
return weak
? 'W/' + tag
: tag
总结
对的,我们已经讲完如何生成ETag了,是不是又加深了对http的理解,那就快快关注我,让我们一起继续后面的jshttp之旅吧