概述
在 HTTP 协议中,请求头 If-Match
、If-None-Match
、If-Modified-Since
、If-Unmodified-Since
、If-Range
主要是为了解决浏览器缓存数据而定义的请求头标准,按照协议规范正确的判断和使用这几个请求头,可以更精准的处理浏览器缓存,从而达到提高系统性能和减少系统带宽的占用的目的。
更精准的处理 Web 缓存效果是可以很明显的:
- 1、 减少了网络交互,加快页面响应速度,增强用户体验;
- 2、 减少了网络带宽消耗,因为没有更新的资源就不需要重复返回了,特别是图片、视频、下载文件这类大响应体请求;
当请求中存在上述 If-xxx
时,服务器对附加的条件进行判断,当判定条件为真,才会执行标准的数据处理和数据返回,否则直接返回对应的HTTP错误码。
针对服务端原始资源是否变更目前有两类处理规则:基于修改时间的(If-Modified-Since
、If-Unmodified-Since
)和基于自定义标识的(If-Match
、If-None-Match
),还有一个是处理文件断电续传使用到的 If-Range
。
经常做服务端开发的会发现,基于时间的并不能很精准的进行缓存判断,有些场景下后端资源可能在1秒钟以内进行了变更,时间请求头只精确到秒,是不足以覆盖这种场景的。还有一些场景是我们没有定义修改时间的,可能是基于其他标志记录是否被修改的。这种情况下,我们使用 If-Match
、If-None-Match
来进行资源是否变更的更精准判断,这两个头基于一个自定义字符串传送,这个字符串你可以自己定义,例如用 md5,时间戳都可以,需要注意它俩需要结合 ETag
请求头一起使用(ETag 指代一个独一无二的版本号字符串,称为“实体标签”)。
下文针对 If-Match、If-None-Match 和 ETag
的交互原理及使用方法进行说明。
详解
服务端对资源记录一个 ETag(实体标记)的字段,当资源更新后ETag也会随之更新。
所以当客户端 If-Match 的值若与服务端的ETag一致,才会执行请求,否则拒绝处理返回412状态码。
交互图:
示例代码:
package com.example.webfluxreactivedemo1.controller;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* HTTP请求头IfMatch和ETag处理
*
* @author 单红宇
* @date 2023/11/2 10:09
*/
@RestController
@RequestMapping
public class IfMatchController {
/**
* 获取资源接口
*
* @param id 资源ID
* @param clientETag 请求头中的 If-None-Match
* @return ResponseEntity
*/
@GetMapping("/resource/{id}")
public ResponseEntity<String> getResource(@PathVariable String id,
@RequestHeader("If-None-Match") String clientETag) {
// 检查资源是否存在以及资源最新的ETag是否与请求头中的If-None-Match匹配
boolean resourceExists = checkResourceExists(id);
boolean etagMatch = checkETagMatch(id, clientETag);
if (!resourceExists) {
// 如果资源不存在,返回404 Not Found
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
} else if (!etagMatch) {
// 如果资源存在且ETag不匹配(即资源已经发生了变更),则返回资源内容
return ResponseEntity.ok().header(HttpHeaders.ETAG, this.generateETag(id)).body("Resource content");
} else {
// 如果资源存在且ETag匹配(即资源没有发生变更),返回304和空响应体
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
}
}
/**
* 修改文章内容
*
* @param id 文章ID
* @param clientETag 请求头中的 If-Match
* @return ResponseEntity
*/
@GetMapping("/updateArticle/{id}")
public ResponseEntity<String> updateArticle(@PathVariable String id,
@RequestHeader("If-Match") String clientETag) {
// 检查资源是否存在以及资源最新的ETag是否与请求头中的If-Match匹配
boolean etagMatch = checkETagMatch(id, clientETag);
if (etagMatch) {
// 如果资源存在且ETag匹配,即文章没有被其他人修改过,执行更新操作
String newETag = "返回文章最新的ETag";
// articleService.update(id);
return ResponseEntity.ok().header(HttpHeaders.ETAG, newETag).body("修改成功");
} else {
// 如果ETag不匹配,说明文章被其他人修改过,用户需要获取最新内容后再基于最新内容修改提交,防止多人同时修改文章内容出现覆盖问题
// 返回412 Precondition Failed
return ResponseEntity.status(HttpStatus.PRECONDITION_FAILED).build();
}
}
// 如果资源存在但ETag不匹配,返回412 Precondition Failed
/**
* 检查资源是否存在
*
* @param id 资源ID
* @return true=存在
*/
private boolean checkResourceExists(String id) {
// 在这里实现检查资源是否存在的逻辑
return true;
}
/**
* 检查资源ETag是否匹配,即判定资源的ETag是否发生了变动
*
* @param id 资源ID
* @param clientETag 浏览器客户端传过来的ETag
* @return 当资源已经被更新时返回false,资源未更新返回true
*/
private boolean checkETagMatch(String id, String clientETag) {
// 在这里实现检查资源ETag是否与请求头中的If-Match/If-None-Match匹配的逻辑
return true;
}
/**
* 生成一个ETag
*
* @param resourceId 资源ID
* @return ETag
*/
public String generateETag(String resourceId) {
// 在这里实现检查资源是否存在的逻辑
String eTag = "根据resourceId按照自己的逻辑生成etag,比如你可以使用md5";
// 注意ETag必须使用双引号包起来返回,这是HTTP协议规范要求
return "\"" + eTag + "\"";
}
}
常见误区
以下是关于这两个字段的一些常见误区:
-
错误的使用方式:有些开发者可能会错误地将If-None-Match和If-Match混淆或颠倒使用。例如,本应使用If-None-Match来检查缓存有效性的情况下使用了If-Match,这可能导致不必要的请求失败。
-
不了解Etag的工作机制:Etag是一个与特定资源关联的确定值,通常由服务器生成并存储。当资源发生变化时,Etag也会相应地更新。而有些开发者可能误认为Etag是由客户端生成和管理的,这可能导致无法正确使用If-Match或If-None-Match。
-
不正确的Etag格式:Etag的格式应该是ASCII字符串,可能包含一个
"W/"前缀
来表示弱比较算法。有些开发者可能会忽略这一点,导致Etag格式不正确,从而影响缓存控制的效果。
为了避免这些错误,建议开发者仔细阅读HTTP规范,确保正确理解和使用If-Match和If-None-Match字段。同时,也需要了解和掌握Etag的工作机制和正确的使用方法。
(END)