From 2f78c72f920f67763174c88f897dc269277d8ca1 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 6 Jan 2020 17:48:29 +0100 Subject: [PATCH] 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 --- .../web/servlet/error/BasicErrorController.java | 8 ++++++++ .../BasicErrorControllerIntegrationTests.java | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java index 020db1c578..6cf53da48c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java @@ -32,6 +32,8 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; 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.servlet.ModelAndView; @@ -102,6 +104,12 @@ public class BasicErrorController extends AbstractErrorController { return new ResponseEntity<>(body, status); } + @ExceptionHandler(HttpMediaTypeNotAcceptableException.class) + public ResponseEntity mediaTypeNotAcceptable(HttpServletRequest request) { + HttpStatus status = getStatus(request); + return ResponseEntity.status(status).build(); + } + /** * Determine if the stacktrace attribute should be included. * @param request the source request diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerIntegrationTests.java index 2cf92c11d9..7d510bea5b 100755 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorControllerIntegrationTests.java @@ -200,6 +200,17 @@ class BasicErrorControllerIntegrationTests { 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 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, String message, String path) { assertThat(content.get("status")).as("Wrong status").isEqualTo(status); @@ -298,6 +309,11 @@ class BasicErrorControllerIntegrationTests { throw new InsufficientStorageException(); } + @RequestMapping(path = "/incompatibleType", produces = "text/plain") + String incompatibleType() { + throw new ExpectedException(); + } + @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Expected!") @SuppressWarnings("serial") static class ExpectedException extends RuntimeException {