|
|
|
|
@ -30,108 +30,123 @@ module.exports = fresh
|
|
|
|
|
* @public
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// 判断缓存中的资源是否新鲜的函数,接收请求头和响应头作为参数
|
|
|
|
|
function fresh (reqHeaders, resHeaders) {
|
|
|
|
|
// fields
|
|
|
|
|
var modifiedSince = reqHeaders['if-modified-since']
|
|
|
|
|
var noneMatch = reqHeaders['if-none-match']
|
|
|
|
|
// 从请求头中获取 if-modified-since 字段的值,用于后续判断资源是否有修改
|
|
|
|
|
var modifiedSince = reqHeaders['if-modified-since'];
|
|
|
|
|
// 从请求头中获取 if-none-match 字段的值,用于基于实体标签(ETag)验证资源是否变化
|
|
|
|
|
var noneMatch = reqHeaders['if-none-match'];
|
|
|
|
|
|
|
|
|
|
// unconditional request
|
|
|
|
|
if (!modifiedSince && !noneMatch) {
|
|
|
|
|
return false
|
|
|
|
|
// 如果 if-modified-since 和 if-none-match 字段都不存在,说明是无条件请求,直接返回 false,表示资源不是新鲜的,需重新获取资源
|
|
|
|
|
if (!modifiedSince &&!noneMatch) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Always return stale when Cache-Control: no-cache
|
|
|
|
|
// to support end-to-end reload requests
|
|
|
|
|
// https://tools.ietf.org/html/rfc2616#section-14.9.4
|
|
|
|
|
var cacheControl = reqHeaders['cache-control']
|
|
|
|
|
// 从请求头中获取 cache-control 字段的值,用于判断是否有 no-cache 指令
|
|
|
|
|
var cacheControl = reqHeaders['cache-control'];
|
|
|
|
|
// 如果 cache-control 字段存在且匹配表示 no-cache 的正则表达式(这里假设 CACHE_CONTROL_NO_CACHE_REGEXP 在外部已定义好),则返回 false,意味着要忽略缓存,重新获取资源,遵循相关网络协议标准
|
|
|
|
|
if (cacheControl && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl)) {
|
|
|
|
|
return false
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if-none-match
|
|
|
|
|
if (noneMatch && noneMatch !== '*') {
|
|
|
|
|
var etag = resHeaders['etag']
|
|
|
|
|
// 如果 if-none-match 字段存在且不等于通配符 *,则进行基于 ETag 的验证
|
|
|
|
|
if (noneMatch && noneMatch!== '*') {
|
|
|
|
|
// 从响应头中获取 etag(实体标签)的值,用于和请求头中的 if-none-match 进行匹配对比
|
|
|
|
|
var etag = resHeaders['etag'];
|
|
|
|
|
|
|
|
|
|
// 如果响应头中没有 etag,则返回 false,即资源不是新鲜的
|
|
|
|
|
if (!etag) {
|
|
|
|
|
return false
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var etagStale = true
|
|
|
|
|
var matches = parseTokenList(noneMatch)
|
|
|
|
|
// 先假设基于 ETag 的验证是不通过的(资源是陈旧的),后续通过循环对比来更新这个状态
|
|
|
|
|
var etagStale = true;
|
|
|
|
|
// 调用 parseTokenList 函数解析 if-none-match 字段的值为一个令牌列表,方便后续逐个对比
|
|
|
|
|
var matches = parseTokenList(noneMatch);
|
|
|
|
|
for (var i = 0; i < matches.length; i++) {
|
|
|
|
|
var match = matches[i]
|
|
|
|
|
var match = matches[i];
|
|
|
|
|
// 将列表中的每个值与 etag 进行比较(考虑了弱验证等不同格式匹配情况,如 W/ 开头的弱验证格式),只要有匹配的就将 etagStale 设为 false,表示资源是新鲜的(基于 ETag 验证通过)
|
|
|
|
|
if (match === etag || match === 'W/' + etag || 'W/' + match === etag) {
|
|
|
|
|
etagStale = false
|
|
|
|
|
break
|
|
|
|
|
etagStale = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果经过遍历对比后,基于 ETag 的验证还是不通过(etagStale 为 true),则返回 false,即资源不是新鲜的
|
|
|
|
|
if (etagStale) {
|
|
|
|
|
return false
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if-modified-since
|
|
|
|
|
// 如果 if-modified-since 字段存在,则进行基于资源最后修改时间的验证
|
|
|
|
|
if (modifiedSince) {
|
|
|
|
|
var lastModified = resHeaders['last-modified']
|
|
|
|
|
var modifiedStale = !lastModified || !(parseHttpDate(lastModified) <= parseHttpDate(modifiedSince))
|
|
|
|
|
// 从响应头中获取 last-modified(资源最后修改时间)的值
|
|
|
|
|
var lastModified = resHeaders['last-modified'];
|
|
|
|
|
// 判断资源是否陈旧(modifiedStale),如果 lastModified 不存在或者通过 parseHttpDate 函数解析后的 lastModified 时间大于 modifiedSince 时间(即资源在 if-modified-since 所指定的时间之后有修改),就认为资源是陈旧的(modifiedStale 为 true)
|
|
|
|
|
var modifiedStale =!lastModified ||!(parseHttpDate(lastModified) <= parseHttpDate(modifiedSince));
|
|
|
|
|
|
|
|
|
|
// 如果资源是陈旧的(modifiedStale 为 true),则返回 false,即资源不是新鲜的
|
|
|
|
|
if (modifiedStale) {
|
|
|
|
|
return false
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
// 如果经过前面所有的验证条件都通过了,最后返回 true,表示资源是新鲜的,可以使用缓存
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Parse an HTTP Date into a number.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} date
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Parse an HTTP Date into a number.
|
|
|
|
|
// 将一个 HTTP 日期格式的字符串解析为时间戳(数字形式),该函数是私有函数(可能只在内部使用)
|
|
|
|
|
function parseHttpDate (date) {
|
|
|
|
|
var timestamp = date && Date.parse(date)
|
|
|
|
|
// 尝试使用 Date.parse 方法解析传入的日期字符串,将解析结果赋给 timestamp 变量
|
|
|
|
|
var timestamp = date && Date.parse(date);
|
|
|
|
|
|
|
|
|
|
// istanbul ignore next: guard against date.js Date.parse patching
|
|
|
|
|
// 通过条件判断,如果 timestamp 的类型是 number,就返回该时间戳,否则返回 NaN,处理 Date.parse 可能出现的解析失败情况,同时告诉测试框架(如 Istanbul)忽略下面这行代码的覆盖情况(可能因外部对 Date.parse 修改等特殊情况不好测试)
|
|
|
|
|
return typeof timestamp === 'number'
|
|
|
|
|
? timestamp
|
|
|
|
|
: NaN
|
|
|
|
|
? timestamp
|
|
|
|
|
: NaN;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Parse a HTTP token list.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} str
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Parse a HTTP token list.
|
|
|
|
|
// 解析一个类似 HTTP 令牌列表格式的字符串,该函数是私有函数(可能只在内部使用)
|
|
|
|
|
function parseTokenList (str) {
|
|
|
|
|
var end = 0
|
|
|
|
|
var list = []
|
|
|
|
|
var start = 0
|
|
|
|
|
var end = 0;
|
|
|
|
|
var list = [];
|
|
|
|
|
var start = 0;
|
|
|
|
|
|
|
|
|
|
// gather tokens
|
|
|
|
|
// 遍历字符串中的每个字符,根据字符的编码来判断如何划分令牌(token)
|
|
|
|
|
for (var i = 0, len = str.length; i < len; i++) {
|
|
|
|
|
switch (str.charCodeAt(i)) {
|
|
|
|
|
case 0x20: /* */
|
|
|
|
|
// 如果字符编码是空格(0x20),且当前开始位置和结束位置相同(说明连续的空格开头情况),就更新开始和结束位置为下一个字符位置
|
|
|
|
|
if (start === end) {
|
|
|
|
|
start = end = i + 1
|
|
|
|
|
start = end = i + 1;
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
case 0x2c: /* , */
|
|
|
|
|
list.push(str.substring(start, end))
|
|
|
|
|
start = end = i + 1
|
|
|
|
|
break
|
|
|
|
|
break;
|
|
|
|
|
case 0x2c: /*, */
|
|
|
|
|
// 如果字符编码是逗号(0x2c),就将从 start 到 end 位置的子字符串作为一个令牌添加到 list 数组中,并更新 start 和 end 位置为下一个字符位置
|
|
|
|
|
list.push(str.substring(start, end));
|
|
|
|
|
start = end = i + 1;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
end = i + 1
|
|
|
|
|
break
|
|
|
|
|
// 对于其他字符情况,就更新 end 位置到下一个字符
|
|
|
|
|
end = i + 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// final token
|
|
|
|
|
list.push(str.substring(start, end))
|
|
|
|
|
// 将最后一个划分出来的令牌(从 start 到 end 位置的子字符串)添加到 list 数组中
|
|
|
|
|
list.push(str.substring(start, end));
|
|
|
|
|
|
|
|
|
|
return list
|
|
|
|
|
// 返回解析好的令牌列表数组,用于后续对类似 if-none-match 等字段值的进一步处理
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|