前言
紧接上文SpringBoot处理静态文件源码分析,分析下其中处理静态文件时的缓存机制
http协议
http协议有一条规则:
- 当response header中携带Last-Modified时,当再次发起一个相同请求时会把Last-Modified的值放到request header的If-Modified-Since字段中
- 当服务端返回http状态码为304时就会从当前缓存中获取资源
源码
基于上述协议我们来看SpringBoot中的源码
直接看ResourceHttpRequestHandler的handleRequest,ResourceHttpRequestHandler是处理静态文件的处理器,不了解的可以从上一篇文章开始看
@Overridepublic 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 phaseif (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {logger.trace("Resource not modified");return;}// Apply cache settings, if anyprepareResponse(response);// Check the media type for the resourceMediaType mediaType = getMediaType(request, resource);// Content phaseif (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
@Overridepublic boolean checkNotModified(long lastModifiedTimestamp) {return checkNotModified(null, lastModifiedTimestamp);}
@Overridepublic boolean checkNotModified(@Nullable String etag, long lastModifiedTimestamp) {......boolean validated = validateIfNoneMatch(etag);if (!validated) {// 核心代码1validateIfModifiedSince(lastModifiedTimestamp);}// Update responseif (response != null) {boolean isHttpGetOrHead = SAFE_METHODS.contains(getRequest().getMethod());if (this.notModified) {// 核心代码2response.setStatus(isHttpGetOrHead ?HttpStatus.NOT_MODIFIED.value() : HttpStatus.PRECONDITION_FAILED.value());}if (isHttpGetOrHead) {if (lastModifiedTimestamp > 0 && parseDateValue(response.getHeader(LAST_MODIFIED)) == -1) {// 核心代码3response.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协议的规范