HTTP ETag是服务器为特定版本的资源生成的唯一标识字符串,当客户端再次请求该资源时,可通过携带ETag相关信息发起条件请求,服务器根据标识判断资源是否变更,从而决定返回新资源还是告知客户端使用本地缓存。而重定向是服务器引导客户端访问其他URL的响应机制,两者在复杂请求场景中会产生特定的交互逻辑。

ETag的基础工作原理
ETag由服务器在响应资源时通过ETag响应头返回,常见的生成方式包括资源内容的哈希值、最后修改时间戳的编码组合等。当客户端需要再次请求该资源时,会根据之前的缓存策略,在请求头中携带If-None-Match字段,值为之前获取的ETag值,向服务器发起条件请求。
服务器收到条件请求后,会将请求头中的If-None-Match值与当前资源的ETag进行比对:
- 如果两者一致,说明资源未变更,服务器返回
304 Not Modified状态码,响应体为空,客户端直接使用本地缓存的资源。 - 如果两者不一致,说明资源已更新,服务器返回
200 OK状态码,响应体中携带新的资源内容,同时更新ETag响应头。
ETag相关的基础请求示例
以下是服务器生成ETag并返回的Node.js示例代码:
const http = require('http');
const crypto = require('crypto');
const server = http.createServer((req, res) => {
// 模拟资源内容
const resourceContent = '这是测试资源内容';
// 生成资源内容的md5哈希作为ETag
const etag = crypto.createHash('md5').update(resourceContent).digest('hex');
// 处理条件请求
if (req.headers['if-none-match'] === etag) {
res.writeHead(304, {
'ETag': etag,
'Cache-Control': 'max-age=300'
});
res.end();
return;
}
res.writeHead(200, {
'Content-Type': 'text/plain',
'ETag': etag,
'Cache-Control': 'max-age=300'
});
res.end(resourceContent);
});
server.listen(3000, () => {
console.log('服务器运行在3000端口');
});
常见重定向类型与特性
HTTP重定向通过3xx系列状态码实现,不同状态码的交互特性存在差异:
| 状态码 | 名称 | 请求方法是否保持 | 是否会被缓存 |
|---|---|---|---|
| 301 | 永久重定向 | 多数场景下POST会转为GET | 会被浏览器缓存,后续请求直接访问新地址 |
| 302 | 临时重定向 | 多数场景下POST会转为GET | 不会被持久缓存,每次请求先访问原地址 |
| 307 | 临时重定向 | 保持原请求方法不变 | 不会被持久缓存,每次请求先访问原地址 |
| 308 | 永久重定向 | 保持原请求方法不变 | 会被浏览器缓存,后续请求直接访问新地址 |
ETag与重定向的交互逻辑
当请求的资源发生重定向时,ETag的交互需要分场景分析:
场景1:原资源有ETag,重定向到新地址
如果客户端本地缓存了原地址的资源及对应的ETag,发起请求时携带If-None-Match头访问原地址,服务器判断资源已迁移到新地址,返回301/302/307/308状态码,同时在响应头中携带Location指向新地址。此时需要注意:
- 重定向响应本身不会返回
304状态码,因为资源位置已经变更,不属于资源未修改的场景。 - 客户端收到重定向响应后,会向新地址发起请求,此时是否携带原ETag取决于新地址的资源是否和原资源是同一版本。如果新地址的资源是原资源的迁移,服务器可保持ETag一致,客户端后续请求新地址时可正常使用条件请求。
场景2:重定向后的新资源也有ETag
客户端访问新地址的资源时,服务器会返回新资源的ETag,客户端后续请求新地址时,携带对应新资源的If-None-Match值即可正常触发条件请求逻辑。如果新地址的资源是原资源的更新版本,ETag会发生变化,客户端会获取到新的资源内容和ETag。
场景3:条件请求遇到重定向
当客户端发送携带If-None-Match的条件请求到原地址,服务器返回重定向响应时,不会处理ETag比对逻辑,直接返回重定向状态码。因为条件请求的核心是对比当前请求地址的资源是否变更,而重定向意味着请求地址已经不是资源的最终地址,所以无需进行资源版本比对。
开发中的注意事项
- 如果资源发生永久迁移,优先使用301或308状态码,同时保证新旧地址的资源ETag一致,避免客户端重复缓存不同版本的同一资源。
- 临时重定向场景下,不要期望客户端缓存重定向规则,每次原地址的请求都会触发重定向,条件请求的ETag仅在最终资源的请求地址生效。
- 不要在重定向响应中返回
ETag头,因为重定向响应没有携带资源内容,返回的ETag没有实际比对意义,反而可能造成客户端缓存错误的标识。
完整交互流程示例
以下是模拟原地址资源重定向,同时新地址资源支持ETag条件请求的Node.js代码:
const http = require('http');
const crypto = require('crypto');
// 新地址的资源内容
const newResourceContent = '这是迁移后的新资源内容';
const newEtag = crypto.createHash('md5').update(newResourceContent).digest('hex');
const server = http.createServer((req, res) => {
const url = req.url;
// 原地址 /old-resource 处理
if (url === '/old-resource') {
// 返回301永久重定向到新地址
res.writeHead(301, {
'Location': '/new-resource',
'Cache-Control': 'max-age=86400'
});
res.end();
return;
}
// 新地址 /new-resource 处理
if (url === '/new-resource') {
// 处理条件请求
if (req.headers['if-none-match'] === newEtag) {
res.writeHead(304, {
'ETag': newEtag,
'Cache-Control': 'max-age=300'
});
res.end();
return;
}
res.writeHead(200, {
'Content-Type': 'text/plain',
'ETag': newEtag,
'Cache-Control': 'max-age=300'
});
res.end(newResourceContent);
return;
}
res.writeHead(404);
res.end('Not Found');
});
server.listen(3000, () => {
console.log('服务器运行在3000端口');
});
上述代码中,客户端第一次访问/old-resource会收到301重定向,之后访问/new-resource获取资源并得到ETag,后续请求/new-resource时携带对应ETag即可触发304缓存逻辑,而/old-resource的请求会直接被重定向,不会进行ETag比对。