跳至主要內容

13. ShallowEtagHeaderFilter

安图新大约 2 分钟

13. ShallowEtagHeaderFilter

前言

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,资源内容没有发生变化,就可以让浏览器使用缓存。