功能说明
该示例处理 HTTP 条件请求中的 ETag 比较。实体标签可能是强标签(带双引号)或弱标签(W/ 前缀),If-None-Match 使用弱比较,只要 opaque 部分相同即视为匹配;If-Match 使用强比较,两侧都必须不是弱标签。通配符星号表示只要当前存在表示就匹配。常用于静态资源协商缓存、乐观并发控制与避免重复下载。
关键代码
parseETagList 逐字符扫描而不是简单按逗号分割,因为 opaque 标签内部允许出现逗号;扫描时跳过字段间空白,识别可选的 W/ 前缀,再读取到下一个双引号,遇到未闭合或缺少引号立即抛 SyntaxError。strongMatch 额外要求两边都不是弱标签,weakMatch 只比较 opaque。两个条件函数分别遍历解析结果,星号分支单独处理,避免把星号当作普通标签。
使用注意
比较是大小写敏感的,opaque 值不会做解码或规范化。函数只接受字符串,缺少头部时应由调用方传空串或直接判断,空串会解析为空列表并返回 false。损坏的头部选择抛错而不是静默忽略,便于在代理或测试中尽早发现问题。弱比较不能用于需要强一致性的场景,例如断点续传或写操作的 If-Match。
验证方式
构造包含逗号的标签,确认解析不被拆分;用同一 opaque 分别生成强、弱标签,验证 strongMatch 为 false、weakMatch 为 true;测试 If-None-Match 能匹配弱标签而 If-Match 不能;检查星号在 current 为空时返回 false;最后验证缺少引号与未闭合标签会抛 SyntaxError。
代码示例
export function parseETagList(header) {
if (typeof header !== "string") throw new TypeError("header must be a string");
const tags = [];
let index = 0;
while (index < header.length) {
while (index < header.length && (header[index] === "," || /\s/.test(header[index]))) {
index += 1;
}
if (index >= header.length) break;
let weak = false;
if (header.startsWith("W/", index)) {
weak = true;
index += 2;
}
if (header[index] !== '"') throw new SyntaxError("invalid ETag at index " + index);
index += 1;
const start = index;
while (index < header.length && header[index] !== '"') index += 1;
if (index >= header.length) throw new SyntaxError("unterminated ETag");
const opaque = header.slice(start, index);
index += 1;
tags.push({ weak, opaque });
}
return tags;
}
export function strongMatch(left, right) {
return Boolean(left && right && !left.weak && !right.weak && left.opaque === right.opaque);
}
export function weakMatch(left, right) {
return Boolean(left && right && left.opaque === right.opaque);
}
export function matchesIfNoneMatch(header, current) {
if (typeof header !== "string") throw new TypeError("header must be a string");
if (header.trim() === "*") return current != null;
return parseETagList(header).some((tag) => weakMatch(tag, current));
}
export function matchesIfMatch(header, current) {
if (typeof header !== "string") throw new TypeError("header must be a string");
if (header.trim() === "*") return current != null;
return parseETagList(header).some((tag) => strongMatch(tag, current));
}