spring ShallowEtagHeaderFilter 源码
spring ShallowEtagHeaderFilter 代码
文件路径:/spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.filter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;
/**
* {@link jakarta.servlet.Filter} that generates an {@code ETag} value based on the
* content on the response. This ETag is compared to the {@code If-None-Match}
* header of the request. If these headers are equal, the response content is
* not sent, but rather a {@code 304 "Not Modified"} status instead.
*
* <p>Since the ETag is based on the response content, the response
* (e.g. a {@link org.springframework.web.servlet.View}) is still rendered.
* As such, this filter only saves bandwidth, not server performance.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Brian Clozel
* @author Juergen Hoeller
* @since 3.0
*/
public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
private static final String DIRECTIVE_NO_STORE = "no-store";
private static final String STREAMING_ATTRIBUTE = ShallowEtagHeaderFilter.class.getName() + ".STREAMING";
private boolean writeWeakETag = false;
/**
* Set whether the ETag value written to the response should be weak, as per RFC 7232.
* <p>Should be configured using an {@code <init-param>} for parameter name
* "writeWeakETag" in the filter definition in {@code web.xml}.
* @since 4.3
* @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">RFC 7232 section 2.3</a>
*/
public void setWriteWeakETag(boolean writeWeakETag) {
this.writeWeakETag = writeWeakETag;
}
/**
* Return whether the ETag value written to the response should be weak, as per RFC 7232.
* @since 4.3
*/
public boolean isWriteWeakETag() {
return this.writeWeakETag;
}
/**
* The default value is {@code false} so that the filter may delay the generation
* of an ETag until the last asynchronously dispatched thread.
*/
@Override
protected boolean shouldNotFilterAsyncDispatch() {
return false;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpServletResponse responseToUse = response;
if (!isAsyncDispatch(request) && !(response instanceof ConditionalContentCachingResponseWrapper)) {
responseToUse = new ConditionalContentCachingResponseWrapper(response, request);
}
filterChain.doFilter(request, responseToUse);
if (!isAsyncStarted(request) && !isContentCachingDisabled(request)) {
updateResponse(request, responseToUse);
}
}
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();
if (isEligibleForEtag(request, wrapper, wrapper.getStatus(), wrapper.getContentInputStream())) {
String eTag = wrapper.getHeader(HttpHeaders.ETAG);
if (!StringUtils.hasText(eTag)) {
eTag = generateETagHeaderValue(wrapper.getContentInputStream(), this.writeWeakETag);
rawResponse.setHeader(HttpHeaders.ETAG, eTag);
}
if (new ServletWebRequest(request, rawResponse).checkNotModified(eTag)) {
return;
}
}
wrapper.copyBodyToResponse();
}
/**
* Whether an ETag should be calculated for the given request and response
* exchange. By default, this is {@code true} if all the following match:
* <ul>
* <li>Response is not committed.</li>
* <li>Response status codes is in the {@code 2xx} series.</li>
* <li>Request method is a GET.</li>
* <li>Response Cache-Control header does not contain "no-store" (or is not present at all).</li>
* </ul>
* @param request the HTTP request
* @param response the HTTP response
* @param responseStatusCode the HTTP response status code
* @param inputStream the response body
* @return {@code true} if eligible for ETag generation, {@code false} otherwise
*/
protected boolean isEligibleForEtag(HttpServletRequest request, HttpServletResponse response,
int responseStatusCode, InputStream inputStream) {
if (!response.isCommitted() &&
responseStatusCode >= 200 && responseStatusCode < 300 &&
HttpMethod.GET.matches(request.getMethod())) {
String cacheControl = response.getHeader(HttpHeaders.CACHE_CONTROL);
return (cacheControl == null || !cacheControl.contains(DIRECTIVE_NO_STORE));
}
return false;
}
/**
* Generate the ETag header value from the given response body byte array.
* <p>The default implementation generates an MD5 hash.
* @param inputStream the response body as an InputStream
* @param isWeak whether the generated ETag should be weak
* @return the ETag header value
* @see org.springframework.util.DigestUtils
*/
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();
}
/**
* This method can be used to suppress the content caching response wrapper
* of the ShallowEtagHeaderFilter. The main reason for this is streaming
* scenarios which are not to be cached and do not need an eTag.
* <p><strong>Note:</strong> This method must be called before the response
* is written to in order for the entire response content to be written
* without caching.
* @since 4.2
*/
public static void disableContentCaching(ServletRequest request) {
Assert.notNull(request, "ServletRequest must not be null");
request.setAttribute(STREAMING_ATTRIBUTE, true);
}
private static boolean isContentCachingDisabled(HttpServletRequest request) {
return (request.getAttribute(STREAMING_ATTRIBUTE) != null);
}
/**
* Returns the raw OutputStream, instead of the one that does caching,
* if {@link #isContentCachingDisabled}.
*/
private static class ConditionalContentCachingResponseWrapper extends ContentCachingResponseWrapper {
private final HttpServletRequest request;
ConditionalContentCachingResponseWrapper(HttpServletResponse response, HttpServletRequest request) {
super(response);
this.request = request;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return (isContentCachingDisabled(this.request) || hasETag() ?
getResponse().getOutputStream() : super.getOutputStream());
}
@Override
public PrintWriter getWriter() throws IOException {
return (isContentCachingDisabled(this.request) || hasETag()?
getResponse().getWriter() : super.getWriter());
}
private boolean hasETag() {
return StringUtils.hasText(getHeader(HttpHeaders.ETAG));
}
}
}
相关信息
相关文章
spring AbstractRequestLoggingFilter 源码
spring CharacterEncodingFilter 源码
spring CommonsRequestLoggingFilter 源码
spring DelegatingFilterProxy 源码
0
赞
热门推荐
-
2、 - 优质文章
-
3、 gate.io
-
8、 golang
-
9、 openharmony
-
10、 Vue中input框自动聚焦