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
This commit is contained in:
Brian Clozel 2024-05-23 11:25:08 +02:00
parent 94348d9d41
commit 51f6e78e25
2 changed files with 22 additions and 1 deletions

View File

@ -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.InvocableHandlerMethod;
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver; import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod; import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod;
import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
/** /**
@ -416,7 +417,15 @@ class ControllerMethodResolver {
public InvocableHandlerMethod getExceptionHandlerMethod(Throwable ex, ServerWebExchange exchange, @Nullable HandlerMethod handlerMethod) { public InvocableHandlerMethod getExceptionHandlerMethod(Throwable ex, ServerWebExchange exchange, @Nullable HandlerMethod handlerMethod) {
Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null); Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);
List<MediaType> requestedMediaTypes = this.contentTypeResolver.resolveMediaTypes(exchange); List<MediaType> 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 // Controller-local first
if (handlerType != null) { if (handlerType != null) {

View File

@ -258,6 +258,18 @@ class ControllerMethodResolverTests {
assertThat(producibleMediaTypes).isNotEmpty().contains(MediaType.APPLICATION_JSON); 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( private static HandlerMethodArgumentResolver next(
List<? extends HandlerMethodArgumentResolver> resolvers, AtomicInteger index) { List<? extends HandlerMethodArgumentResolver> resolvers, AtomicInteger index) {