From 51f6e78e25254cc53ffb53796c8e47aafae15f11 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Thu, 23 May 2024 11:25:08 +0200 Subject: [PATCH] Fall back on */* during content negotiation for errors Prior to this commit, gh-31936 enabled content negotiation for `@ExceptionHandler` annotated methods in Spring MVC and WebFlux. In the case of WebFlux, HTTP clients sending invalid media types in the "Accept" request header would fail with a `NotAcceptableStatusException` This exception would be handled with an HTTP 406 response status, instead of processing the original exception. This commit ensures that invalid media types are ignored during the exception handling phase and that we fall back to "*/*". Fixes gh-32878 --- .../method/annotation/ControllerMethodResolver.java | 11 ++++++++++- .../annotation/ControllerMethodResolverTests.java | 12 ++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java index aac9d48ea3..d4011e05a8 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java @@ -60,6 +60,7 @@ import org.springframework.web.reactive.result.method.HandlerMethodArgumentResol import org.springframework.web.reactive.result.method.InvocableHandlerMethod; import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver; import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod; +import org.springframework.web.server.NotAcceptableStatusException; import org.springframework.web.server.ServerWebExchange; /** @@ -416,7 +417,15 @@ class ControllerMethodResolver { public InvocableHandlerMethod getExceptionHandlerMethod(Throwable ex, ServerWebExchange exchange, @Nullable HandlerMethod handlerMethod) { Class handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null); - List requestedMediaTypes = this.contentTypeResolver.resolveMediaTypes(exchange); + List requestedMediaTypes = List.of(MediaType.ALL); + try { + requestedMediaTypes = this.contentTypeResolver.resolveMediaTypes(exchange); + } + catch (NotAcceptableStatusException exc) { + if (logger.isDebugEnabled()) { + logger.debug("Could not parse Accept header for requested media types", exc); + } + } // Controller-local first if (handlerType != null) { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolverTests.java index 7d582a95d3..f45585a26c 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolverTests.java @@ -258,6 +258,18 @@ class ControllerMethodResolverTests { assertThat(producibleMediaTypes).isNotEmpty().contains(MediaType.APPLICATION_JSON); } + @Test + void exceptionHandlerWithInvalidAcceptHeader() { + Method method = ResolvableMethod.on(ExceptionHandlerController.class).mockCall(ExceptionHandlerController::handle).method(); + this.handlerMethod = new HandlerMethod(new ExceptionHandlerController(), method); + MockServerHttpRequest httpRequest = MockServerHttpRequest.get("/test").header("Accept", "v=12").build(); + MockServerWebExchange serverWebExchange = MockServerWebExchange.builder(httpRequest).build(); + InvocableHandlerMethod invocable = this.methodResolver.getExceptionHandlerMethod( + new ResponseStatusException(HttpStatus.BAD_REQUEST, "reason"), serverWebExchange, this.handlerMethod); + + assertThat(invocable).as("No match").isNotNull(); + } + private static HandlerMethodArgumentResolver next( List resolvers, AtomicInteger index) {