Avoid 406 Not Acceptable for error pages

Prior to this commit, the `ErrorController` would override the original
error response status if the error map cannot be written due to content
negotiation with the HTTP client. In that case, the error handling
infrastructure returns a `406 Not Acceptable` response.

This commit improves the `ErrorController` so that
`HttpMediaTypeNotAcceptableException` instances thrown by that
controller are not returned as is but instead we write the error
response with an empty body and the original HTTP error status.

Fixes gh-19545
See gh-19522
pull/19690/head
Brian Clozel 5 years ago
parent 6a7e3c55b8
commit 2f78c72f92

@ -32,6 +32,8 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
@ -102,6 +104,12 @@ public class BasicErrorController extends AbstractErrorController {
return new ResponseEntity<>(body, status); return new ResponseEntity<>(body, status);
} }
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
public ResponseEntity<String> mediaTypeNotAcceptable(HttpServletRequest request) {
HttpStatus status = getStatus(request);
return ResponseEntity.status(status).build();
}
/** /**
* Determine if the stacktrace attribute should be included. * Determine if the stacktrace attribute should be included.
* @param request the source request * @param request the source request

@ -200,6 +200,17 @@ class BasicErrorControllerIntegrationTests {
assertThat(resp).contains("We are out of storage"); assertThat(resp).contains("We are out of storage");
} }
@Test
void testIncompatibleMediaType() {
load();
RequestEntity<?> request = RequestEntity.get(URI.create(createUrl("/incompatibleType")))
.accept(MediaType.TEXT_PLAIN).build();
ResponseEntity<String> entity = new TestRestTemplate().exchange(request, String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
assertThat(entity.getHeaders().getContentType()).isNull();
assertThat(entity.getBody()).isNull();
}
private void assertErrorAttributes(Map<?, ?> content, String status, String error, Class<?> exception, private void assertErrorAttributes(Map<?, ?> content, String status, String error, Class<?> exception,
String message, String path) { String message, String path) {
assertThat(content.get("status")).as("Wrong status").isEqualTo(status); assertThat(content.get("status")).as("Wrong status").isEqualTo(status);
@ -298,6 +309,11 @@ class BasicErrorControllerIntegrationTests {
throw new InsufficientStorageException(); throw new InsufficientStorageException();
} }
@RequestMapping(path = "/incompatibleType", produces = "text/plain")
String incompatibleType() {
throw new ExpectedException();
}
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Expected!") @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Expected!")
@SuppressWarnings("serial") @SuppressWarnings("serial")
static class ExpectedException extends RuntimeException { static class ExpectedException extends RuntimeException {

Loading…
Cancel
Save