vary 源码阅读

技术 5月 06, 2021

概述

vary 库功能介绍

vary 主要功能是为了方便在响应头上添加 Vary 头信息。

HTTP 响应头 Vary 介绍

响应头 Vary 主要是用于缓存的控制。源服务器会通过 Vary 告诉代理服务器如何使用缓存。
当代理服务器接收到源服务器带有 Vary 响应头的响应后,若要对其进行缓存,则该缓存只对 Vary 指定的请求头字段相同的请求生效。所以即使对相同资源发起请求,但由于 Vary 指定的请求头字段不相同,也无法使用缓存,必须从源服务器中重新获取资源。

基本使用

vary

直接对 response 添加 vary 头信息

// 类型声明
declare function vary(res: ServerResponse, field: string | string[]): void;

// 使用
http.createServer(function(req, res) {
  //...
  vary(res, 'User-Agent')
  //...
})

vary.append

直接在已有的 vary 头信息上添加新的字段信息,合并两个 vary 字段

// 类型声明
declare namespace vary {
    function append(header: string, field: string | string[]): string;
}
  
// 使用
http.createServer(function(req, res) {
  //...
  const vary = vary.append('Accept, User-Agent', 'Origin')
  res.setHeader
  //...
})

源码阅读

parse

主要对已有的 vary 字段信息进行结构,由字符串信息变成更好处理的数组。
eg: 'Origin, Accept, Origin' => ['Origin', 'Accept', 'Origin']

function parse (header: string): string[] {
  var end = 0
  var start = 0
  var list: string[] = []
  
  // gather tokens
  // 类似于词法分析中的直接扫描法
  for (var i = 0, len = header.length; i < len; i++) {
  	// 遍历字符串
    switch (header.charCodeAt(i)) {
      // 可以忽略字段开头和结尾的空格,但不会忽略字段中间的空格  
      case 0x20: /*   */
        if (start === end) {
          start = end = i + 1
        }
        break
     	// 根据 ',' 拆分字段,并加入集合
      case 0x2c: /* , */
        list.push(header.substring(start, end))
        start = end = i + 1
        break
      // 正常字符则略过
      default:
        end = i + 1
        break
    }
  }

  // final token
  // 可能有残留字段,并加入集合
  list.push(header.substring(start, end))

  return list
}

vary.append

function append (header, field) {
  // 入参校验
  if (typeof header !== 'string') {
    throw new TypeError('header argument is required')
  }

  // 入参校验
  if (!field) {
    throw new TypeError('field argument is required')
  }

  // get fields array
  // 解析需要添加的字段成数组
  var fields = !Array.isArray(field)
    ? parse(String(field))
    : field

  // assert on invalid field names
  // 字段验证
  for (var j = 0; j < fields.length; j++) {
    if (!FIELD_NAME_REGEXP.test(fields[j])) {
      throw new TypeError('field argument contains an invalid header name')
    }
  }

  // existing, unspecified vary
  // 如果本来的 vary 是 '*' (所有的请求都被视为唯一并且非缓存的),则忽略合并并返回
  if (header === '*') {
    return header
  }

  // enumerate current values
  // 解析原有的 vary 字段成数组
  var val = header
  var vals = parse(header.toLowerCase())

  // 与上面一个判断同等意义,如果有 vary 字段为 '*',则直接返回 '*'
  if (fields.indexOf('*') !== -1 || vals.indexOf('*') !== -1) {
    return '*'
  }

  // 合并字段
  for (var i = 0; i < fields.length; i++) {
    var fld = fields[i].toLowerCase()

    // append value (case-preserving)
    if (vals.indexOf(fld) === -1) {
      vals.push(fld)
      val = val
        ? val + ', ' + fields[i]
        : fields[i]
    }
  }

  return val
}

vary

function vary (res, field) {
  // 判断 res 是否为 SeverResponse 
  if (!res || !res.getHeader || !res.setHeader) {
    // quack quack
    throw new TypeError('res argument is required')
  }

  // get existing header
  // 获取原有的 Vary 头信息,并字符串化
  var val = res.getHeader('Vary') || ''
  var header = Array.isArray(val)
    ? val.join(', ')
    : String(val)

  // set new header
  // 调用 append 合并字段,并设置为新值
  if ((val = append(header, field))) {
    res.setHeader('Vary', val)
  }
}

参考

Vary | MDN
HTTP 协议中 Vary 的一些研究

Pengsha Ying

逝者如斯,故不舍昼夜