Skip to content

Commit a98bf30

Browse files
committed
ShallowEtagHeaderFilter checks for pre-existing eTag
The filter now checks for an explicitly set eTag and uses it instead of generating one, and also suppresses caching. Closes gh-24635
1 parent c7e037d commit a98bf30

File tree

2 files changed

+57
-8
lines changed

2 files changed

+57
-8
lines changed

spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import org.springframework.http.HttpMethod;
3232
import org.springframework.util.Assert;
3333
import org.springframework.util.DigestUtils;
34+
import org.springframework.util.StringUtils;
35+
import org.springframework.web.context.request.ServletWebRequest;
3436
import org.springframework.web.util.ContentCachingResponseWrapper;
3537
import org.springframework.web.util.WebUtils;
3638

@@ -117,11 +119,12 @@ private void updateResponse(HttpServletRequest request, HttpServletResponse resp
117119
HttpServletResponse rawResponse = (HttpServletResponse) wrapper.getResponse();
118120

119121
if (isEligibleForEtag(request, wrapper, wrapper.getStatus(), wrapper.getContentInputStream())) {
120-
String responseETag = generateETagHeaderValue(wrapper.getContentInputStream(), this.writeWeakETag);
121-
rawResponse.setHeader(HttpHeaders.ETAG, responseETag);
122-
String requestETag = request.getHeader(HttpHeaders.IF_NONE_MATCH);
123-
if (requestETag != null && ("*".equals(requestETag) || compareETagHeaderValue(requestETag, responseETag))) {
124-
rawResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
122+
String eTag = wrapper.getHeader(HttpHeaders.ETAG);
123+
if (!StringUtils.hasText(eTag)) {
124+
eTag = generateETagHeaderValue(wrapper.getContentInputStream(), this.writeWeakETag);
125+
rawResponse.setHeader(HttpHeaders.ETAG, eTag);
126+
}
127+
if (new ServletWebRequest(request, rawResponse).checkNotModified(eTag)) {
125128
return;
126129
}
127130
}
@@ -224,15 +227,19 @@ private static class ConditionalContentCachingResponseWrapper extends ContentCac
224227

225228
@Override
226229
public ServletOutputStream getOutputStream() throws IOException {
227-
return (isContentCachingDisabled(this.request) ?
230+
return (isContentCachingDisabled(this.request) || hasETag() ?
228231
getResponse().getOutputStream() : super.getOutputStream());
229232
}
230233

231234
@Override
232235
public PrintWriter getWriter() throws IOException {
233-
return (isContentCachingDisabled(this.request) ?
236+
return (isContentCachingDisabled(this.request) || hasETag()?
234237
getResponse().getWriter() : super.getWriter());
235238
}
239+
240+
private boolean hasETag() {
241+
return StringUtils.hasText(getHeader(HttpHeaders.ETAG));
242+
}
236243
}
237244

238245
}

spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,15 +19,21 @@
1919
import java.io.Serializable;
2020
import java.lang.reflect.Method;
2121
import java.util.ArrayList;
22+
import java.util.Collections;
2223
import java.util.List;
2324

25+
import javax.servlet.FilterChain;
26+
import javax.servlet.http.HttpServletRequest;
27+
import javax.servlet.http.HttpServletResponse;
28+
2429
import com.fasterxml.jackson.annotation.JsonTypeInfo;
2530
import com.fasterxml.jackson.annotation.JsonTypeName;
2631
import org.junit.jupiter.api.BeforeEach;
2732
import org.junit.jupiter.api.Test;
2833

2934
import org.springframework.core.MethodParameter;
3035
import org.springframework.http.HttpEntity;
36+
import org.springframework.http.HttpHeaders;
3137
import org.springframework.http.MediaType;
3238
import org.springframework.http.ResponseEntity;
3339
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
@@ -42,6 +48,7 @@
4248
import org.springframework.web.bind.support.WebDataBinderFactory;
4349
import org.springframework.web.context.request.NativeWebRequest;
4450
import org.springframework.web.context.request.ServletWebRequest;
51+
import org.springframework.web.filter.ShallowEtagHeaderFilter;
4552
import org.springframework.web.method.HandlerMethod;
4653
import org.springframework.web.method.support.ModelAndViewContainer;
4754
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
@@ -200,6 +207,41 @@ public void handleReturnValueCharSequence() throws Exception {
200207
assertThat(servletResponse.getContentAsString()).isEqualTo("Foo");
201208
}
202209

210+
@Test // SPR-13423
211+
public void handleReturnValueWithETagAndETagFilter() throws Exception {
212+
213+
String eTagValue = "\"deadb33f8badf00d\"";
214+
String content = "body";
215+
216+
Method method = getClass().getDeclaredMethod("handle");
217+
MethodParameter returnType = new MethodParameter(method, -1);
218+
219+
FilterChain chain = (req, res) -> {
220+
ResponseEntity<String> returnValue = ResponseEntity.ok().eTag(eTagValue).body(content);
221+
try {
222+
ServletWebRequest requestToUse =
223+
new ServletWebRequest((HttpServletRequest) req, (HttpServletResponse) res);
224+
225+
new HttpEntityMethodProcessor(Collections.singletonList(new StringHttpMessageConverter()))
226+
.handleReturnValue(returnValue, returnType, mavContainer, requestToUse);
227+
228+
assertThat(this.servletResponse.getContentAsString())
229+
.as("Response body was cached? It should be written directly to the raw response")
230+
.isEqualTo(content);
231+
}
232+
catch (Exception ex) {
233+
throw new IllegalStateException(ex);
234+
}
235+
};
236+
237+
this.servletRequest.setMethod("GET");
238+
new ShallowEtagHeaderFilter().doFilter(this.servletRequest, this.servletResponse, chain);
239+
240+
assertThat(this.servletResponse.getStatus()).isEqualTo(200);
241+
assertThat(this.servletResponse.getHeader(HttpHeaders.ETAG)).isEqualTo(eTagValue);
242+
assertThat(this.servletResponse.getContentAsString()).isEqualTo(content);
243+
}
244+
203245

204246
@SuppressWarnings("unused")
205247
private void handle(HttpEntity<List<SimpleBean>> arg1, HttpEntity<SimpleBean> arg2) {

0 commit comments

Comments
 (0)