后会无期之jshttp解析(2)

724 阅读4分钟

从小到大都是优,你让我怎么从良

昨天解读了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之旅吧