前言
紧接上文SpringBoot处理静态文件源码分析,分析下其中处理静态文件时的缓存机制
http协议
http协议有一条规则:
- 当response header中携带Last-Modified时,当再次发起一个相同请求时会把Last-Modified的值放到request header的If-Modified-Since字段中
- 当服务端返回http状态码为304时就会从当前缓存中获取资源
源码
基于上述协议我们来看SpringBoot中的源码
直接看ResourceHttpRequestHandler的handleRequest,ResourceHttpRequestHandler是处理静态文件的处理器,不了解的可以从上一篇文章开始看
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// For very general mappings (e.g. "/") we need to check 404 first
// 获取资源文件
Resource resource = getResource(request);
// 核心代码
// Header phase
if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {
logger.trace("Resource not modified");
return;
}
// Apply cache settings, if any
prepareResponse(response);
// Check the media type for the resource
MediaType mediaType = getMediaType(request, resource);
// Content phase
if (METHOD_HEAD.equals(request.getMethod())) {
setHeaders(response, resource, mediaType);
return;
}
ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
if (request.getHeader(HttpHeaders.RANGE) == null) {
Assert.state(this.resourceHttpMessageConverter != null, "Not initialized");
setHeaders(response, resource, mediaType);
this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
}
......
}
上述的核心方法就是checkNotModified
@Override
public boolean checkNotModified(long lastModifiedTimestamp) {
return checkNotModified(null, lastModifiedTimestamp);
}
@Override
public boolean checkNotModified(@Nullable String etag, long lastModifiedTimestamp) {
......
boolean validated = validateIfNoneMatch(etag);
if (!validated) {
// 核心代码1
validateIfModifiedSince(lastModifiedTimestamp);
}
// Update response
if (response != null) {
boolean isHttpGetOrHead = SAFE_METHODS.contains(getRequest().getMethod());
if (this.notModified) {
// 核心代码2
response.setStatus(isHttpGetOrHead ?
HttpStatus.NOT_MODIFIED.value() : HttpStatus.PRECONDITION_FAILED.value());
}
if (isHttpGetOrHead) {
if (lastModifiedTimestamp > 0 && parseDateValue(response.getHeader(LAST_MODIFIED)) == -1) {
// 核心代码3
response.setDateHeader(LAST_MODIFIED, lastModifiedTimestamp);
}
if (StringUtils.hasLength(etag) && response.getHeader(ETAG) == null) {
response.setHeader(ETAG, padEtagIfNecessary(etag));
}
}
}
return this.notModified;
}
核心代码1,validateIfModifiedSince方法,lastModifiedTimestamp的值是当前资源文件的最后修改时间
private boolean validateIfModifiedSince(long lastModifiedTimestamp) {
if (lastModifiedTimestamp < 0) {
return false;
}
// IF_MODIFIED_SINCE = "If-Modified-Since"
long ifModifiedSince = parseDateHeader(IF_MODIFIED_SINCE);
if (ifModifiedSince == -1) {
return false;
}
// We will perform this validation...
this.notModified = ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000);
return true;
}
简单的说就是获取request中的If-Modified-Since,如果这个值不为空并且大于等于资源文件的最近修改时间,那么就相当于文件没有被修改。当满足条件时修改notModified 的值为true
核心代码2处,当notModified 为true时修改http的状态码为HttpStatus.NOT_MODIFIED.value(),值为304
核心代码3处,response.setDateHeader(LAST_MODIFIED, lastModifiedTimestamp);
LAST_MODIFIED = “Last-Modified”
在response的header上设置Last-Modified值,当浏览器接收到该参数后,再次发起请求就会在request中传递If-Modified-Since值了
到这里就源码就分析完了
实践
自己实践下,写个方法
@ApiOperation("cache")
@RequestMapping("/cache")
public String cache(HttpServletRequest request, HttpServletResponse httpServletResponse) {
httpServletResponse.addDateHeader("Last-Modified",System.currentTimeMillis());
String since = request.getHeader("If-Modified-Since");
if (StringUtils.isNotEmpty(since)) {
httpServletResponse.setStatus(304);
return "当前走了缓存,所以我不会返回";
}
return "返回" + System.currentTimeMillis();
}
浏览器多次访问,第一次返回http状态码是200,并且response携带了Last-Modified字段
接下去访问结果
可以看到用到了缓存,因为值没有发生改变,并且request的header中携带了If-Modified-Since参数
总结
SpringBoot中处理静态资源的处理器使用了验证文件是否修改的缓存机制,其机制的实现依赖于http协议的规范