前言

ShallowEtagHeaderFilter用于处理Etag,HTTP1.1用ETag来判断请求的文件是否被修改,如果未修改则返回304,让浏览器使用缓存的数据。

一、注册

@Configuration
public class FilterConfiguration {
   
     

    @Bean
    public FilterRegistrationBean filterTestRegistrationBean(){
   
     
        FilterRegistrationBean filterRegistry = new FilterRegistrationBean();
        filterRegistry.setOrder(Ordered.HIGHEST_PRECEDENCE + 3);
        //注册过滤器
        filterRegistry.setFilter(new ShallowEtagHeaderFilter());
        filterRegistry.addUrlPatterns("/*");
        filterRegistry.setName("eTagFilter");
        return filterRegistry;
    }

}

二、流程

1、doFilterInternal( )

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
   
     

		HttpServletResponse responseToUse = response;
		if (!isAsyncDispatch(request) && !(response instanceof ConditionalContentCachingResponseWrapper)) {
   
     
			//包装response、request
			responseToUse = new ConditionalContentCachingResponseWrapper(response, request);
		}
		//执行过滤器连
		filterChain.doFilter(request, responseToUse);
		
		if (!isAsyncStarted(request) && !isContentCachingDisabled(request)) {
   
     
			updateResponse(request, responseToUse);
		}
	}

2、updateResponse( )

private void updateResponse(HttpServletRequest request, HttpServletResponse response) throws IOException {
   
     
		ConditionalContentCachingResponseWrapper wrapper =
				WebUtils.getNativeResponse(response, ConditionalContentCachingResponseWrapper.class);
		Assert.notNull(wrapper, "ContentCachingResponseWrapper not found");
		HttpServletResponse rawResponse = (HttpServletResponse) wrapper.getResponse();
		//校验Etag
		if (isEligibleForEtag(request, wrapper, wrapper.getStatus(), wrapper.getContentInputStream())) {
   
     
			String eTag = wrapper.getHeader(HttpHeaders.ETAG);
			if (!StringUtils.hasText(eTag)) {
   
     
				//不存在时,生成Etag
				eTag = generateETagHeaderValue(wrapper.getContentInputStream(), this.writeWeakETag);
				//设置响应header
				rawResponse.setHeader(HttpHeaders.ETAG, eTag);
			}
			//校验Etag,资源Etag不变时,return
			if (new ServletWebRequest(request, rawResponse).checkNotModified(eTag)) {
   
     
				return;
			}
		}
		//Etag改变时,将响应题写入Response 响应给浏览器
		wrapper.copyBodyToResponse();
	}

3、isEligibleForEtag( )

protected boolean isEligibleForEtag(HttpServletRequest request, HttpServletResponse response,
			int responseStatusCode, InputStream inputStream) {
   
     
		
		//response没有完成,响应码在200~300之间,并且是get请求
		if (!response.isCommitted() &&
				responseStatusCode >= 200 && responseStatusCode < 300 &&
				HttpMethod.GET.matches(request.getMethod())) {
   
     
			
			String cacheControl = response.getHeader(HttpHeaders.CACHE_CONTROL);
			//并且cacheControl为null或者cacheControl不包含no-store
			return (cacheControl == null || !cacheControl.contains(DIRECTIVE_NO_STORE));
		}

		return false;
	}

4、generateETagHeaderValue( )

对资源的inputStream进行md5计算

protected String generateETagHeaderValue(InputStream inputStream, boolean isWeak) throws IOException {
   
     
		// length of W/ + " + 0 + 32bits md5 hash + "
		StringBuilder builder = new StringBuilder(37);
		if (isWeak) {
   
     
			builder.append("W/");
		}
		builder.append("\"0");
		DigestUtils.appendMd5DigestAsHex(inputStream, builder);
		builder.append('"');
		return builder.toString();
	}

5、checkNotModified( )

定位到Etag的校验

validateIfNoneMatch(etag)

private boolean validateIfNoneMatch(@Nullable String etag) {
   
     
		if (!StringUtils.hasLength(etag)) {
   
     
			return false;
		}

		Enumeration<String> ifNoneMatch;
		try {
   
     
			//获取请求header中的If-None-Match
			ifNoneMatch = getRequest().getHeaders(HttpHeaders.IF_NONE_MATCH);
		}
		catch (IllegalArgumentException ex) {
   
     
			return false;
		}
		if (!ifNoneMatch.hasMoreElements()) {
   
     
			return false;
		}

		// We will perform this validation...
		etag = padEtagIfNecessary(etag);
		if (etag.startsWith("W/")) {
   
     
			etag = etag.substring(2);
		}
		while (ifNoneMatch.hasMoreElements()) {
   
     
			String clientETags = ifNoneMatch.nextElement();
			//正则出Etag
			Matcher etagMatcher = ETAG_HEADER_VALUE_PATTERN.matcher(clientETags);
			// Compare weak/strong ETags as per https://tools.ietf.org/html/rfc7232#section-2.3
			while (etagMatcher.find()) {
   
     
				//通过equals进行值的比对
				if (StringUtils.hasLength(etagMatcher.group()) && etag.equals(etagMatcher.group(3))) {
   
     
					//匹配到任意一个Etag
					this.notModified = true;
					break;
				}
			}
		}

		return true;
	}

总结

通过Etag,资源内容没有发生变化,就可以让浏览器使用缓存。