Fix missing ETag/LastModified headers in responses
Prior to this commit, the `HttpEntityMethodProcessor` would avoid writing ETag/Last-Modified response headers before calling `ServletWebRequest` to process conditional requests. This was done to avoid duplicate response header values due to headers being already written to the underlying servlet response. This is still necessary for GET/HEAD requests, since this is properly handled by `ServletWebRequest` for those cases. But `HttpEntityMethodProcessor` should not make that decision for PUT/PATCH/POST responses since developers are adding response headers on purpose and should be in control of the situation — whereas `ServletWebRequest` does not write those headers in those cases. Issue: SPR-14767
This commit is contained in:
@@ -28,6 +28,7 @@ import org.springframework.core.MethodParameter;
|
|||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.RequestEntity;
|
import org.springframework.http.RequestEntity;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
@@ -183,7 +184,7 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
|
|||||||
if (responseEntity instanceof ResponseEntity) {
|
if (responseEntity instanceof ResponseEntity) {
|
||||||
int returnStatus = ((ResponseEntity<?>) responseEntity).getStatusCodeValue();
|
int returnStatus = ((ResponseEntity<?>) responseEntity).getStatusCodeValue();
|
||||||
outputMessage.getServletResponse().setStatus(returnStatus);
|
outputMessage.getServletResponse().setStatus(returnStatus);
|
||||||
if(returnStatus == 200) {
|
if (returnStatus == 200) {
|
||||||
if (isResourceNotModified(inputMessage, outputMessage)) {
|
if (isResourceNotModified(inputMessage, outputMessage)) {
|
||||||
// Ensure headers are flushed, no body should be written.
|
// Ensure headers are flushed, no body should be written.
|
||||||
outputMessage.flush();
|
outputMessage.flush();
|
||||||
@@ -227,8 +228,10 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
|
|||||||
HttpHeaders responseHeaders = outputMessage.getHeaders();
|
HttpHeaders responseHeaders = outputMessage.getHeaders();
|
||||||
String etag = responseHeaders.getETag();
|
String etag = responseHeaders.getETag();
|
||||||
long lastModifiedTimestamp = responseHeaders.getLastModified();
|
long lastModifiedTimestamp = responseHeaders.getLastModified();
|
||||||
responseHeaders.remove(HttpHeaders.ETAG);
|
if (inputMessage.getMethod() == HttpMethod.GET || inputMessage.getMethod() == HttpMethod.HEAD) {
|
||||||
responseHeaders.remove(HttpHeaders.LAST_MODIFIED);
|
responseHeaders.remove(HttpHeaders.ETAG);
|
||||||
|
responseHeaders.remove(HttpHeaders.LAST_MODIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
return servletWebRequest.checkNotModified(etag, lastModifiedTimestamp);
|
return servletWebRequest.checkNotModified(etag, lastModifiedTimestamp);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ import java.util.Locale;
|
|||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
@@ -68,6 +70,9 @@ import static org.springframework.web.servlet.HandlerMapping.*;
|
|||||||
*/
|
*/
|
||||||
public class HttpEntityMethodProcessorMockTests {
|
public class HttpEntityMethodProcessorMockTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
private SimpleDateFormat dateFormat;
|
private SimpleDateFormat dateFormat;
|
||||||
|
|
||||||
private HttpEntityMethodProcessor processor;
|
private HttpEntityMethodProcessor processor;
|
||||||
@@ -77,14 +82,23 @@ public class HttpEntityMethodProcessorMockTests {
|
|||||||
private HttpMessageConverter<Resource> resourceMessageConverter;
|
private HttpMessageConverter<Resource> resourceMessageConverter;
|
||||||
|
|
||||||
private MethodParameter paramHttpEntity;
|
private MethodParameter paramHttpEntity;
|
||||||
|
|
||||||
private MethodParameter paramRequestEntity;
|
private MethodParameter paramRequestEntity;
|
||||||
|
|
||||||
private MethodParameter paramResponseEntity;
|
private MethodParameter paramResponseEntity;
|
||||||
|
|
||||||
private MethodParameter paramInt;
|
private MethodParameter paramInt;
|
||||||
|
|
||||||
private MethodParameter returnTypeResponseEntity;
|
private MethodParameter returnTypeResponseEntity;
|
||||||
|
|
||||||
private MethodParameter returnTypeResponseEntityProduces;
|
private MethodParameter returnTypeResponseEntityProduces;
|
||||||
|
|
||||||
private MethodParameter returnTypeResponseEntityResource;
|
private MethodParameter returnTypeResponseEntityResource;
|
||||||
|
|
||||||
private MethodParameter returnTypeHttpEntity;
|
private MethodParameter returnTypeHttpEntity;
|
||||||
|
|
||||||
private MethodParameter returnTypeHttpEntitySubclass;
|
private MethodParameter returnTypeHttpEntitySubclass;
|
||||||
|
|
||||||
private MethodParameter returnTypeInt;
|
private MethodParameter returnTypeInt;
|
||||||
|
|
||||||
private ModelAndViewContainer mavContainer;
|
private ModelAndViewContainer mavContainer;
|
||||||
@@ -153,7 +167,7 @@ public class HttpEntityMethodProcessorMockTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void resolveArgument() throws Exception {
|
public void shouldResolveHttpEntityArgument() throws Exception {
|
||||||
String body = "Foo";
|
String body = "Foo";
|
||||||
|
|
||||||
MediaType contentType = MediaType.TEXT_PLAIN;
|
MediaType contentType = MediaType.TEXT_PLAIN;
|
||||||
@@ -171,7 +185,7 @@ public class HttpEntityMethodProcessorMockTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void resolveArgumentRequestEntity() throws Exception {
|
public void shouldResolveRequestEntityArgument() throws Exception {
|
||||||
String body = "Foo";
|
String body = "Foo";
|
||||||
|
|
||||||
MediaType contentType = MediaType.TEXT_PLAIN;
|
MediaType contentType = MediaType.TEXT_PLAIN;
|
||||||
@@ -196,8 +210,8 @@ public class HttpEntityMethodProcessorMockTests {
|
|||||||
assertEquals("Invalid argument", body, requestEntity.getBody());
|
assertEquals("Invalid argument", body, requestEntity.getBody());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = HttpMediaTypeNotSupportedException.class)
|
@Test
|
||||||
public void resolveArgumentNotReadable() throws Exception {
|
public void shouldFailResolvingWhenConverterCannotRead() throws Exception {
|
||||||
MediaType contentType = MediaType.TEXT_PLAIN;
|
MediaType contentType = MediaType.TEXT_PLAIN;
|
||||||
servletRequest.setMethod("POST");
|
servletRequest.setMethod("POST");
|
||||||
servletRequest.addHeader("Content-Type", contentType.toString());
|
servletRequest.addHeader("Content-Type", contentType.toString());
|
||||||
@@ -205,27 +219,24 @@ public class HttpEntityMethodProcessorMockTests {
|
|||||||
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(contentType));
|
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(contentType));
|
||||||
given(stringHttpMessageConverter.canRead(String.class, contentType)).willReturn(false);
|
given(stringHttpMessageConverter.canRead(String.class, contentType)).willReturn(false);
|
||||||
|
|
||||||
|
this.thrown.expect(HttpMediaTypeNotSupportedException.class);
|
||||||
processor.resolveArgument(paramHttpEntity, mavContainer, webRequest, null);
|
processor.resolveArgument(paramHttpEntity, mavContainer, webRequest, null);
|
||||||
|
|
||||||
fail("Expected exception");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = HttpMediaTypeNotSupportedException.class)
|
|
||||||
public void resolveArgumentNoContentType() throws Exception {
|
|
||||||
servletRequest.setMethod("POST");
|
|
||||||
servletRequest.setContent("some content".getBytes(StandardCharsets.UTF_8));
|
|
||||||
processor.resolveArgument(paramHttpEntity, mavContainer, webRequest, null);
|
|
||||||
fail("Expected exception");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleReturnValue() throws Exception {
|
public void shouldFailResolvingWhenContentTypeNotSupported() throws Exception {
|
||||||
|
servletRequest.setMethod("POST");
|
||||||
|
servletRequest.setContent("some content".getBytes(StandardCharsets.UTF_8));
|
||||||
|
this.thrown.expect(HttpMediaTypeNotSupportedException.class);
|
||||||
|
processor.resolveArgument(paramHttpEntity, mavContainer, webRequest, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldHandleReturnValue() throws Exception {
|
||||||
String body = "Foo";
|
String body = "Foo";
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>(body, HttpStatus.OK);
|
ResponseEntity<String> returnValue = new ResponseEntity<>(body, HttpStatus.OK);
|
||||||
|
|
||||||
MediaType accepted = MediaType.TEXT_PLAIN;
|
MediaType accepted = MediaType.TEXT_PLAIN;
|
||||||
servletRequest.addHeader("Accept", accepted.toString());
|
servletRequest.addHeader("Accept", accepted.toString());
|
||||||
|
|
||||||
initStringMessageConversion(accepted);
|
initStringMessageConversion(accepted);
|
||||||
|
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
@@ -235,13 +246,11 @@ public class HttpEntityMethodProcessorMockTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleReturnValueProduces() throws Exception {
|
public void shouldHandleReturnValueWithProducibleMediaType() throws Exception {
|
||||||
String body = "Foo";
|
String body = "Foo";
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>(body, HttpStatus.OK);
|
ResponseEntity<String> returnValue = new ResponseEntity<>(body, HttpStatus.OK);
|
||||||
|
|
||||||
servletRequest.addHeader("Accept", "text/*");
|
servletRequest.addHeader("Accept", "text/*");
|
||||||
servletRequest.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.TEXT_HTML));
|
servletRequest.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.TEXT_HTML));
|
||||||
|
|
||||||
given(stringHttpMessageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
|
given(stringHttpMessageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
|
||||||
|
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntityProduces, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntityProduces, mavContainer, webRequest);
|
||||||
@@ -252,12 +261,10 @@ public class HttpEntityMethodProcessorMockTests {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Test
|
@Test
|
||||||
public void handleReturnValueWithResponseBodyAdvice() throws Exception {
|
public void shouldHandleReturnValueWithResponseBodyAdvice() throws Exception {
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>(HttpStatus.OK);
|
|
||||||
|
|
||||||
servletRequest.addHeader("Accept", "text/*");
|
servletRequest.addHeader("Accept", "text/*");
|
||||||
servletRequest.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.TEXT_HTML));
|
servletRequest.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.TEXT_HTML));
|
||||||
|
ResponseEntity<String> returnValue = new ResponseEntity<>(HttpStatus.OK);
|
||||||
ResponseBodyAdvice<String> advice = mock(ResponseBodyAdvice.class);
|
ResponseBodyAdvice<String> advice = mock(ResponseBodyAdvice.class);
|
||||||
given(advice.supports(any(), any())).willReturn(true);
|
given(advice.supports(any(), any())).willReturn(true);
|
||||||
given(advice.beforeBodyWrite(any(), any(), any(), any(), any(), any())).willReturn("Foo");
|
given(advice.beforeBodyWrite(any(), any(), any(), any(), any(), any())).willReturn("Foo");
|
||||||
@@ -274,28 +281,24 @@ public class HttpEntityMethodProcessorMockTests {
|
|||||||
verify(stringHttpMessageConverter).write(eq("Foo"), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
|
verify(stringHttpMessageConverter).write(eq("Foo"), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = HttpMediaTypeNotAcceptableException.class)
|
@Test
|
||||||
public void handleReturnValueNotAcceptable() throws Exception {
|
public void shouldFailHandlingWhenContentTypeNotSupported() throws Exception {
|
||||||
String body = "Foo";
|
String body = "Foo";
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>(body, HttpStatus.OK);
|
ResponseEntity<String> returnValue = new ResponseEntity<>(body, HttpStatus.OK);
|
||||||
|
|
||||||
MediaType accepted = MediaType.APPLICATION_ATOM_XML;
|
MediaType accepted = MediaType.APPLICATION_ATOM_XML;
|
||||||
servletRequest.addHeader("Accept", accepted.toString());
|
servletRequest.addHeader("Accept", accepted.toString());
|
||||||
|
|
||||||
given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true);
|
given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true);
|
||||||
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||||
given(stringHttpMessageConverter.canWrite(String.class, accepted)).willReturn(false);
|
|
||||||
|
|
||||||
|
this.thrown.expect(HttpMediaTypeNotAcceptableException.class);
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
fail("Expected exception");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = HttpMediaTypeNotAcceptableException.class)
|
@Test
|
||||||
public void handleReturnValueNotAcceptableProduces() throws Exception {
|
public void shouldFailHandlingWhenConverterCannotWrite() throws Exception {
|
||||||
String body = "Foo";
|
String body = "Foo";
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>(body, HttpStatus.OK);
|
ResponseEntity<String> returnValue = new ResponseEntity<>(body, HttpStatus.OK);
|
||||||
|
|
||||||
MediaType accepted = MediaType.TEXT_PLAIN;
|
MediaType accepted = MediaType.TEXT_PLAIN;
|
||||||
servletRequest.addHeader("Accept", accepted.toString());
|
servletRequest.addHeader("Accept", accepted.toString());
|
||||||
|
|
||||||
@@ -303,24 +306,21 @@ public class HttpEntityMethodProcessorMockTests {
|
|||||||
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||||
given(stringHttpMessageConverter.canWrite(String.class, accepted)).willReturn(false);
|
given(stringHttpMessageConverter.canWrite(String.class, accepted)).willReturn(false);
|
||||||
|
|
||||||
|
this.thrown.expect(HttpMediaTypeNotAcceptableException.class);
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntityProduces, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntityProduces, mavContainer, webRequest);
|
||||||
|
|
||||||
fail("Expected exception");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SPR-9142
|
@Test // SPR-9142
|
||||||
|
public void shouldFailHandlingWhenAcceptHeaderIllegal() throws Exception {
|
||||||
@Test(expected = HttpMediaTypeNotAcceptableException.class)
|
|
||||||
public void handleReturnValueNotAcceptableParseError() throws Exception {
|
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>("Body", HttpStatus.ACCEPTED);
|
ResponseEntity<String> returnValue = new ResponseEntity<>("Body", HttpStatus.ACCEPTED);
|
||||||
servletRequest.addHeader("Accept", "01");
|
servletRequest.addHeader("Accept", "01");
|
||||||
|
|
||||||
|
this.thrown.expect(HttpMediaTypeNotAcceptableException.class);
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
fail("Expected exception");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleReturnValueResponseHeaderNoBody() throws Exception {
|
public void shouldHandleResponseHeaderNoBody() throws Exception {
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.set("headerName", "headerValue");
|
headers.set("headerName", "headerValue");
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>(headers, HttpStatus.ACCEPTED);
|
ResponseEntity<String> returnValue = new ResponseEntity<>(headers, HttpStatus.ACCEPTED);
|
||||||
@@ -332,7 +332,7 @@ public class HttpEntityMethodProcessorMockTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleReturnValueResponseHeaderAndBody() throws Exception {
|
public void shouldHandleResponseHeaderAndBody() throws Exception {
|
||||||
HttpHeaders responseHeaders = new HttpHeaders();
|
HttpHeaders responseHeaders = new HttpHeaders();
|
||||||
responseHeaders.set("header", "headerValue");
|
responseHeaders.set("header", "headerValue");
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.ACCEPTED);
|
ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.ACCEPTED);
|
||||||
@@ -341,200 +341,149 @@ public class HttpEntityMethodProcessorMockTests {
|
|||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
ArgumentCaptor<HttpOutputMessage> outputMessage = ArgumentCaptor.forClass(HttpOutputMessage.class);
|
ArgumentCaptor<HttpOutputMessage> outputMessage = ArgumentCaptor.forClass(HttpOutputMessage.class);
|
||||||
verify(stringHttpMessageConverter).write(eq("body"), eq(MediaType.TEXT_PLAIN), outputMessage.capture());
|
verify(stringHttpMessageConverter).write(eq("body"), eq(MediaType.TEXT_PLAIN), outputMessage.capture());
|
||||||
assertTrue(mavContainer.isRequestHandled());
|
assertTrue(mavContainer.isRequestHandled());
|
||||||
assertEquals("headerValue", outputMessage.getValue().getHeaders().get("header").get(0));
|
assertEquals("headerValue", outputMessage.getValue().getHeaders().get("header").get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleReturnValueLastModified() throws Exception {
|
public void shouldHandleLastModifiedWithHttp304() throws Exception {
|
||||||
long currentTime = new Date().getTime();
|
long currentTime = new Date().getTime();
|
||||||
long oneMinuteAgo = currentTime - (1000 * 60);
|
long oneMinuteAgo = currentTime - (1000 * 60);
|
||||||
servletRequest.addHeader(HttpHeaders.IF_MODIFIED_SINCE, dateFormat.format(currentTime));
|
servletRequest.addHeader(HttpHeaders.IF_MODIFIED_SINCE, dateFormat.format(currentTime));
|
||||||
HttpHeaders responseHeaders = new HttpHeaders();
|
ResponseEntity<String> returnValue = ResponseEntity.ok().lastModified(oneMinuteAgo).body("body");
|
||||||
responseHeaders.setDate(HttpHeaders.LAST_MODIFIED, oneMinuteAgo);
|
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
|
|
||||||
|
|
||||||
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
assertResponseNotModified();
|
assertConditionalResponse(HttpStatus.NOT_MODIFIED, null, null, oneMinuteAgo);
|
||||||
assertEquals(1, servletResponse.getHeaderValues(HttpHeaders.LAST_MODIFIED).size());
|
|
||||||
assertEquals(dateFormat.format(oneMinuteAgo), servletResponse.getHeader(HttpHeaders.LAST_MODIFIED));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleReturnValueEtag() throws Exception {
|
public void handleEtagWithHttp304() throws Exception {
|
||||||
String etagValue = "\"deadb33f8badf00d\"";
|
String etagValue = "\"deadb33f8badf00d\"";
|
||||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, etagValue);
|
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, etagValue);
|
||||||
HttpHeaders responseHeaders = new HttpHeaders();
|
ResponseEntity<String> returnValue = ResponseEntity.ok().eTag(etagValue).body("body");
|
||||||
responseHeaders.set(HttpHeaders.ETAG, etagValue);
|
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
|
|
||||||
|
|
||||||
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
assertResponseNotModified();
|
assertConditionalResponse(HttpStatus.NOT_MODIFIED, null, etagValue, -1);
|
||||||
assertEquals(1, servletResponse.getHeaderValues(HttpHeaders.ETAG).size());
|
|
||||||
assertEquals(etagValue, servletResponse.getHeader(HttpHeaders.ETAG));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // SPR-14559
|
@Test // SPR-14559
|
||||||
public void handleReturnValueEtagInvalidIfNoneMatch() throws Exception {
|
public void shouldHandleInvalidIfNoneMatchWithHttp200() throws Exception {
|
||||||
String etagValue = "\"deadb33f8badf00d\"";
|
String etagValue = "\"deadb33f8badf00d\"";
|
||||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, "unquoted");
|
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, "unquoted");
|
||||||
HttpHeaders responseHeaders = new HttpHeaders();
|
ResponseEntity<String> returnValue = ResponseEntity.ok().eTag(etagValue).body("body");
|
||||||
responseHeaders.set(HttpHeaders.ETAG, etagValue);
|
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
|
|
||||||
|
|
||||||
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
assertTrue(mavContainer.isRequestHandled());
|
assertConditionalResponse(HttpStatus.OK, "body", etagValue, -1);
|
||||||
assertEquals(HttpStatus.OK.value(), servletResponse.getStatus());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleReturnValueETagAndLastModified() throws Exception {
|
public void shouldHandleETagAndLastModifiedWithHttp304() throws Exception {
|
||||||
long currentTime = new Date().getTime();
|
long currentTime = new Date().getTime();
|
||||||
long oneMinuteAgo = currentTime - (1000 * 60);
|
long oneMinuteAgo = currentTime - (1000 * 60);
|
||||||
String etagValue = "\"deadb33f8badf00d\"";
|
String etagValue = "\"deadb33f8badf00d\"";
|
||||||
servletRequest.addHeader(HttpHeaders.IF_MODIFIED_SINCE, dateFormat.format(currentTime));
|
servletRequest.addHeader(HttpHeaders.IF_MODIFIED_SINCE, dateFormat.format(currentTime));
|
||||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, etagValue);
|
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, etagValue);
|
||||||
HttpHeaders responseHeaders = new HttpHeaders();
|
ResponseEntity<String> returnValue = ResponseEntity.ok().eTag(etagValue).lastModified(oneMinuteAgo).body("body");
|
||||||
responseHeaders.setDate(HttpHeaders.LAST_MODIFIED, oneMinuteAgo);
|
|
||||||
responseHeaders.set(HttpHeaders.ETAG, etagValue);
|
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
|
|
||||||
|
|
||||||
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
assertResponseNotModified();
|
assertConditionalResponse(HttpStatus.NOT_MODIFIED, null, etagValue, oneMinuteAgo);
|
||||||
assertEquals(1, servletResponse.getHeaderValues(HttpHeaders.LAST_MODIFIED).size());
|
|
||||||
assertEquals(dateFormat.format(oneMinuteAgo), servletResponse.getHeader(HttpHeaders.LAST_MODIFIED));
|
|
||||||
assertEquals(1, servletResponse.getHeaderValues(HttpHeaders.ETAG).size());
|
|
||||||
assertEquals(etagValue, servletResponse.getHeader(HttpHeaders.ETAG));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleReturnValueNotModified() throws Exception {
|
public void shouldHandleNotModifiedResponse() throws Exception {
|
||||||
long currentTime = new Date().getTime();
|
long currentTime = new Date().getTime();
|
||||||
long oneMinuteAgo = currentTime - (1000 * 60);
|
long oneMinuteAgo = currentTime - (1000 * 60);
|
||||||
String etagValue = "\"deadb33f8badf00d\"";
|
String etagValue = "\"deadb33f8badf00d\"";
|
||||||
HttpHeaders responseHeaders = new HttpHeaders();
|
ResponseEntity<String> returnValue = ResponseEntity.status(HttpStatus.NOT_MODIFIED)
|
||||||
responseHeaders.setDate(HttpHeaders.LAST_MODIFIED, oneMinuteAgo);
|
.eTag(etagValue).lastModified(oneMinuteAgo).body("body");
|
||||||
responseHeaders.set(HttpHeaders.ETAG, etagValue);
|
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.NOT_MODIFIED);
|
|
||||||
|
|
||||||
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
assertResponseNotModified();
|
assertConditionalResponse(HttpStatus.NOT_MODIFIED, null, etagValue, oneMinuteAgo);
|
||||||
assertEquals(1, servletResponse.getHeaderValues(HttpHeaders.LAST_MODIFIED).size());
|
|
||||||
assertEquals(dateFormat.format(oneMinuteAgo), servletResponse.getHeader(HttpHeaders.LAST_MODIFIED));
|
|
||||||
assertEquals(1, servletResponse.getHeaderValues(HttpHeaders.ETAG).size());
|
|
||||||
assertEquals(etagValue, servletResponse.getHeader(HttpHeaders.ETAG));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleReturnValueChangedETagAndLastModified() throws Exception {
|
public void shouldHandleChangedETagAndLastModified() throws Exception {
|
||||||
long currentTime = new Date().getTime();
|
long currentTime = new Date().getTime();
|
||||||
long oneMinuteAgo = currentTime - (1000 * 60);
|
long oneMinuteAgo = currentTime - (1000 * 60);
|
||||||
String etagValue = "\"deadb33f8badf00d\"";
|
String etagValue = "\"deadb33f8badf00d\"";
|
||||||
String changedEtagValue = "\"changed-etag-value\"";
|
String changedEtagValue = "\"changed-etag-value\"";
|
||||||
servletRequest.addHeader(HttpHeaders.IF_MODIFIED_SINCE, dateFormat.format(currentTime));
|
servletRequest.addHeader(HttpHeaders.IF_MODIFIED_SINCE, dateFormat.format(currentTime));
|
||||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, etagValue);
|
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, etagValue);
|
||||||
HttpHeaders responseHeaders = new HttpHeaders();
|
ResponseEntity<String> returnValue = ResponseEntity.ok()
|
||||||
responseHeaders.setDate(HttpHeaders.LAST_MODIFIED, oneMinuteAgo);
|
.eTag(changedEtagValue).lastModified(oneMinuteAgo).body("body");
|
||||||
responseHeaders.set(HttpHeaders.ETAG, changedEtagValue);
|
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
|
|
||||||
|
|
||||||
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
assertTrue(mavContainer.isRequestHandled());
|
assertConditionalResponse(HttpStatus.OK, null, changedEtagValue, oneMinuteAgo);
|
||||||
assertEquals(HttpStatus.OK.value(), servletResponse.getStatus());
|
|
||||||
assertEquals(1, servletResponse.getHeaderValues(HttpHeaders.LAST_MODIFIED).size());
|
|
||||||
assertEquals(dateFormat.format(oneMinuteAgo), servletResponse.getHeader(HttpHeaders.LAST_MODIFIED));
|
|
||||||
assertEquals(1, servletResponse.getHeaderValues(HttpHeaders.ETAG).size());
|
|
||||||
assertEquals(changedEtagValue, servletResponse.getHeader(HttpHeaders.ETAG));
|
|
||||||
assertEquals(0, servletResponse.getContentAsByteArray().length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SPR-13496
|
@Test // SPR-13496
|
||||||
@Test
|
public void shouldHandleConditionalRequestIfNoneMatchWildcard() throws Exception {
|
||||||
public void handleReturnValuePostRequestWithIfNotModified() throws Exception {
|
|
||||||
String wildcardValue = "*";
|
String wildcardValue = "*";
|
||||||
String etagValue = "\"some-etag\"";
|
String etagValue = "\"some-etag\"";
|
||||||
servletRequest.setMethod("POST");
|
servletRequest.setMethod("POST");
|
||||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, wildcardValue);
|
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, wildcardValue);
|
||||||
HttpHeaders responseHeaders = new HttpHeaders();
|
ResponseEntity<String> returnValue = ResponseEntity.ok().eTag(etagValue).body("body");
|
||||||
responseHeaders.set(HttpHeaders.ETAG, etagValue);
|
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
|
|
||||||
|
|
||||||
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
assertResponseOkWithBody("body");
|
assertConditionalResponse(HttpStatus.OK, "body", etagValue, -1);
|
||||||
assertEquals(0, servletResponse.getHeaderValues(HttpHeaders.ETAG).size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SPR-13626
|
@Test // SPR-13626
|
||||||
@Test
|
public void shouldHandleGetIfNoneMatchWildcard() throws Exception {
|
||||||
public void handleReturnValueGetIfNoneMatchWildcard() throws Exception {
|
|
||||||
String wildcardValue = "*";
|
String wildcardValue = "*";
|
||||||
String etagValue = "\"some-etag\"";
|
String etagValue = "\"some-etag\"";
|
||||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, wildcardValue);
|
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, wildcardValue);
|
||||||
HttpHeaders responseHeaders = new HttpHeaders();
|
ResponseEntity<String> returnValue = ResponseEntity.ok().eTag(etagValue).body("body");
|
||||||
responseHeaders.set(HttpHeaders.ETAG, etagValue);
|
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
|
|
||||||
|
|
||||||
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
assertResponseOkWithBody("body");
|
assertConditionalResponse(HttpStatus.OK, "body", etagValue, -1);
|
||||||
assertEquals(1, servletResponse.getHeaderValues(HttpHeaders.ETAG).size());
|
|
||||||
assertEquals(etagValue, servletResponse.getHeader(HttpHeaders.ETAG));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SPR-13626
|
@Test // SPR-13626
|
||||||
@Test
|
public void shouldHandleIfNoneMatchIfMatch() throws Exception {
|
||||||
public void handleReturnValueIfNoneMatchIfMatch() throws Exception {
|
|
||||||
String etagValue = "\"some-etag\"";
|
String etagValue = "\"some-etag\"";
|
||||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, etagValue);
|
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, etagValue);
|
||||||
servletRequest.addHeader(HttpHeaders.IF_MATCH, "ifmatch");
|
servletRequest.addHeader(HttpHeaders.IF_MATCH, "ifmatch");
|
||||||
HttpHeaders responseHeaders = new HttpHeaders();
|
ResponseEntity<String> returnValue = ResponseEntity.ok().eTag(etagValue).body("body");
|
||||||
responseHeaders.set(HttpHeaders.ETAG, etagValue);
|
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
|
|
||||||
|
|
||||||
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
assertResponseNotModified();
|
assertConditionalResponse(HttpStatus.NOT_MODIFIED, null, etagValue, -1);
|
||||||
assertEquals(1, servletResponse.getHeaderValues(HttpHeaders.ETAG).size());
|
|
||||||
assertEquals(etagValue, servletResponse.getHeader(HttpHeaders.ETAG));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SPR-13626
|
@Test // SPR-13626
|
||||||
@Test
|
public void shouldHandleIfNoneMatchIfUnmodifiedSince() throws Exception {
|
||||||
public void handleReturnValueIfNoneMatchIfUnmodifiedSince() throws Exception {
|
|
||||||
String etagValue = "\"some-etag\"";
|
String etagValue = "\"some-etag\"";
|
||||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, etagValue);
|
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, etagValue);
|
||||||
servletRequest.addHeader(HttpHeaders.IF_UNMODIFIED_SINCE, dateFormat.format(new Date().getTime()));
|
servletRequest.addHeader(HttpHeaders.IF_UNMODIFIED_SINCE, dateFormat.format(new Date().getTime()));
|
||||||
HttpHeaders responseHeaders = new HttpHeaders();
|
ResponseEntity<String> returnValue = ResponseEntity.ok().eTag(etagValue).body("body");
|
||||||
responseHeaders.set(HttpHeaders.ETAG, etagValue);
|
|
||||||
ResponseEntity<String> returnValue = new ResponseEntity<>("body", responseHeaders, HttpStatus.OK);
|
|
||||||
|
|
||||||
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
assertResponseNotModified();
|
assertConditionalResponse(HttpStatus.NOT_MODIFIED, null, etagValue, -1);
|
||||||
assertEquals(1, servletResponse.getHeaderValues(HttpHeaders.ETAG).size());
|
|
||||||
assertEquals(etagValue, servletResponse.getHeader(HttpHeaders.ETAG));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleReturnTypeResource() throws Exception {
|
public void shouldHandleResource() throws Exception {
|
||||||
ResponseEntity<Resource> returnValue = ResponseEntity
|
ResponseEntity<Resource> returnValue = ResponseEntity
|
||||||
.ok(new ByteArrayResource("Content".getBytes(StandardCharsets.UTF_8)));
|
.ok(new ByteArrayResource("Content".getBytes(StandardCharsets.UTF_8)));
|
||||||
|
|
||||||
@@ -549,25 +498,49 @@ public class HttpEntityMethodProcessorMockTests {
|
|||||||
assertEquals(200, servletResponse.getStatus());
|
assertEquals(200, servletResponse.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test //SPR-14767
|
||||||
|
public void shouldHandleValidatorHeadersInPutResponses() throws Exception {
|
||||||
|
servletRequest.setMethod("PUT");
|
||||||
|
String etagValue = "\"some-etag\"";
|
||||||
|
ResponseEntity<String> returnValue = ResponseEntity.ok().header(HttpHeaders.ETAG, etagValue).body("body");
|
||||||
|
|
||||||
|
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
||||||
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
|
assertConditionalResponse(HttpStatus.OK, "body", etagValue, -1);
|
||||||
|
}
|
||||||
|
|
||||||
private void initStringMessageConversion(MediaType accepted) {
|
private void initStringMessageConversion(MediaType accepted) {
|
||||||
given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true);
|
given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true);
|
||||||
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||||
given(stringHttpMessageConverter.canWrite(String.class, accepted)).willReturn(true);
|
given(stringHttpMessageConverter.canWrite(String.class, accepted)).willReturn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertResponseNotModified() {
|
private void assertResponseBody(String body) throws Exception {
|
||||||
assertTrue(mavContainer.isRequestHandled());
|
|
||||||
assertEquals(HttpStatus.NOT_MODIFIED.value(), servletResponse.getStatus());
|
|
||||||
assertEquals(0, servletResponse.getContentAsByteArray().length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertResponseOkWithBody(String body) throws Exception {
|
|
||||||
assertTrue(mavContainer.isRequestHandled());
|
|
||||||
assertEquals(HttpStatus.OK.value(), servletResponse.getStatus());
|
|
||||||
ArgumentCaptor<HttpOutputMessage> outputMessage = ArgumentCaptor.forClass(HttpOutputMessage.class);
|
ArgumentCaptor<HttpOutputMessage> outputMessage = ArgumentCaptor.forClass(HttpOutputMessage.class);
|
||||||
verify(stringHttpMessageConverter).write(eq(body), eq(MediaType.TEXT_PLAIN), outputMessage.capture());
|
verify(stringHttpMessageConverter).write(eq(body), eq(MediaType.TEXT_PLAIN), outputMessage.capture());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertConditionalResponse(HttpStatus status, String body,
|
||||||
|
String etag, long lastModified) throws Exception {
|
||||||
|
assertEquals(status.value(), servletResponse.getStatus());
|
||||||
|
assertTrue(mavContainer.isRequestHandled());
|
||||||
|
if (body != null) {
|
||||||
|
assertResponseBody(body);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assertEquals(0, servletResponse.getContentAsByteArray().length);
|
||||||
|
}
|
||||||
|
if (etag != null) {
|
||||||
|
assertEquals(1, servletResponse.getHeaderValues(HttpHeaders.ETAG).size());
|
||||||
|
assertEquals(etag, servletResponse.getHeader(HttpHeaders.ETAG));
|
||||||
|
}
|
||||||
|
if (lastModified != -1) {
|
||||||
|
assertEquals(1, servletResponse.getHeaderValues(HttpHeaders.LAST_MODIFIED).size());
|
||||||
|
assertEquals(dateFormat.format(lastModified), servletResponse.getHeader(HttpHeaders.LAST_MODIFIED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public ResponseEntity<String> handle1(HttpEntity<String> httpEntity, ResponseEntity<String> entity,
|
public ResponseEntity<String> handle1(HttpEntity<String> httpEntity, ResponseEntity<String> entity,
|
||||||
int i, RequestEntity<String> requestEntity) {
|
int i, RequestEntity<String> requestEntity) {
|
||||||
@@ -597,7 +570,9 @@ public class HttpEntityMethodProcessorMockTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public ResponseEntity<Resource> handle5() {return null;}
|
public ResponseEntity<Resource> handle5() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static class CustomHttpEntity extends HttpEntity<Object> {
|
public static class CustomHttpEntity extends HttpEntity<Object> {
|
||||||
|
|||||||
Reference in New Issue
Block a user