Don't implicitly check preconditions on PUT requests

Prior to this commit, the `HttpEntityMethodProcessor` would check HTTP
conditions on non-safe requests (i.e. not GET/HEAD). This would prevent
Controllers from returning `ResponseEntity` containing response headers
with updated values of `"Last-Modified"` or `ETag` once the resource has
been updated.

This commit avoids those checks for non GET/HEAD requests - this code
can still be leveraged from Controllers themselves using
`ServletWebRequest::checkNotModified` methods.

Issue: SPR-15780
This commit is contained in:
Brian Clozel 2018-03-08 19:43:01 +01:00
parent 50253f670e
commit ed7684d2b2
2 changed files with 25 additions and 3 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@ -21,8 +21,11 @@ import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -66,6 +69,8 @@ import org.springframework.web.servlet.support.RequestContextUtils;
*/
public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodProcessor {
private static final Set<HttpMethod> SAFE_METHODS = EnumSet.of(HttpMethod.GET, HttpMethod.HEAD);
/**
* Basic constructor with converters only. Suitable for resolving
* {@code HttpEntity}. For handling {@code ResponseEntity} consider also
@ -199,7 +204,8 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
int returnStatus = ((ResponseEntity<?>) responseEntity).getStatusCodeValue();
outputMessage.getServletResponse().setStatus(returnStatus);
if (returnStatus == 200) {
if (isResourceNotModified(inputMessage, outputMessage)) {
if (SAFE_METHODS.contains(inputMessage.getMethod())
&& isResourceNotModified(inputMessage, outputMessage)) {
// Ensure headers are flushed, no body should be written.
outputMessage.flush();
// Skip call to converters, as they may update the body.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@ -22,6 +22,7 @@ import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
@ -558,6 +559,21 @@ public class HttpEntityMethodProcessorMockTests {
assertConditionalResponse(HttpStatus.OK, "body", etagValue, -1);
}
@Test
public void shouldNotFailPreconditionForPutRequests() throws Exception {
servletRequest.setMethod("PUT");
ZonedDateTime dateTime = ofEpochMilli(new Date().getTime()).atZone(GMT);
servletRequest.addHeader(HttpHeaders.IF_UNMODIFIED_SINCE, RFC_1123_DATE_TIME.format(dateTime));
long justModified = dateTime.plus(1, ChronoUnit.SECONDS).toEpochSecond() * 1000;
ResponseEntity<String> returnValue = ResponseEntity.ok()
.lastModified(justModified).body("body");
initStringMessageConversion(TEXT_PLAIN);
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
assertConditionalResponse(HttpStatus.OK, null, null, justModified);
}
@Test
public void varyHeader() throws Exception {
String[] entityValues = {"Accept-Language", "User-Agent"};