diff --git a/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java b/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java index 4a3c2fc01c..d9db78b666 100644 --- a/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java +++ b/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -54,7 +54,17 @@ public class HttpMediaTypeNotSupportedException extends HttpMediaTypeException { * @param message the exception message */ public HttpMediaTypeNotSupportedException(String message) { - super(message, Collections.emptyList(), PARSE_ERROR_DETAIL_CODE, null); + this(message, Collections.emptyList()); + } + + /** + * Create a new HttpMediaTypeNotSupportedException for a parse error. + * @param message the exception message + * @param mediaTypes list of supported media types + * @since 6.0.5 + */ + public HttpMediaTypeNotSupportedException(String message, List mediaTypes) { + super(message, mediaTypes, PARSE_ERROR_DETAIL_CODE, null); this.contentType = null; this.httpMethod = null; getBody().setDetail("Could not parse Content-Type."); diff --git a/spring-web/src/main/java/org/springframework/web/server/UnsupportedMediaTypeStatusException.java b/spring-web/src/main/java/org/springframework/web/server/UnsupportedMediaTypeStatusException.java index 2d194236e6..4788a831d7 100644 --- a/spring-web/src/main/java/org/springframework/web/server/UnsupportedMediaTypeStatusException.java +++ b/spring-web/src/main/java/org/springframework/web/server/UnsupportedMediaTypeStatusException.java @@ -57,9 +57,17 @@ public class UnsupportedMediaTypeStatusException extends ResponseStatusException * Constructor for when the specified Content-Type is invalid. */ public UnsupportedMediaTypeStatusException(@Nullable String reason) { + this(reason, Collections.emptyList()); + } + + /** + * Constructor for when the specified Content-Type is invalid. + * @since 6.0.5 + */ + public UnsupportedMediaTypeStatusException(@Nullable String reason, List supportedTypes) { super(HttpStatus.UNSUPPORTED_MEDIA_TYPE, reason, null, PARSE_ERROR_DETAIL_CODE, null); this.contentType = null; - this.supportedMediaTypes = Collections.emptyList(); + this.supportedMediaTypes = Collections.unmodifiableList(supportedTypes); this.bodyType = null; this.method = null; setDetail("Could not parse Content-Type."); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java index 2cc9491867..7da63d855d 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -72,7 +72,8 @@ class DefaultServerRequest implements ServerRequest { ex -> (ex.getContentType() != null ? new UnsupportedMediaTypeStatusException( ex.getContentType(), ex.getSupportedMediaTypes(), ex.getBodyType()) : - new UnsupportedMediaTypeStatusException(ex.getMessage())); + new UnsupportedMediaTypeStatusException( + ex.getMessage(), ex.getSupportedMediaTypes())); private static final Function DECODING_MAPPER = ex -> new ServerWebInputException("Failed to read HTTP message", null, ex); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java index f4b8a60a6d..3494b8bb9a 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -199,7 +199,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe contentType = request.getHeaders().getContentType(); } catch (InvalidMediaTypeException ex) { - throw new UnsupportedMediaTypeStatusException(ex.getMessage()); + throw new UnsupportedMediaTypeStatusException(ex.getMessage(), new ArrayList<>(mediaTypes)); } throw new UnsupportedMediaTypeStatusException( contentType, new ArrayList<>(mediaTypes), exchange.getRequest().getMethod()); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java index ea82434edd..dd2008d608 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -154,7 +154,8 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho } catch (InvalidMediaTypeException ex) { throw new UnsupportedMediaTypeStatusException( - "Can't parse Content-Type [" + headers.getFirst("Content-Type") + "]: " + ex.getMessage()); + "Can't parse Content-Type [" + headers.getFirst("Content-Type") + "]: " + ex.getMessage(), + getSupportedMediaTypes(elementType)); } MediaType mediaType = (contentType != null ? contentType : MediaType.APPLICATION_OCTET_STREAM); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java index ec92c8abd2..a17418c185 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -273,7 +273,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe contentType = MediaType.parseMediaType(request.getContentType()); } catch (InvalidMediaTypeException ex) { - throw new HttpMediaTypeNotSupportedException(ex.getMessage()); + throw new HttpMediaTypeNotSupportedException(ex.getMessage(), new ArrayList<>(mediaTypes)); } } throw new HttpMediaTypeNotSupportedException( diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java index 16142dab5f..717ffa188c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -143,26 +143,27 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements protected Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { + Class contextClass = parameter.getContainingClass(); + Class targetClass = (targetType instanceof Class ? (Class) targetType : null); + if (targetClass == null) { + ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter); + targetClass = (Class) resolvableType.resolve(); + } + MediaType contentType; boolean noContentType = false; try { contentType = inputMessage.getHeaders().getContentType(); } catch (InvalidMediaTypeException ex) { - throw new HttpMediaTypeNotSupportedException(ex.getMessage()); + throw new HttpMediaTypeNotSupportedException( + ex.getMessage(), getSupportedMediaTypes(targetClass != null ? targetClass : Object.class)); } if (contentType == null) { noContentType = true; contentType = MediaType.APPLICATION_OCTET_STREAM; } - Class contextClass = parameter.getContainingClass(); - Class targetClass = (targetType instanceof Class ? (Class) targetType : null); - if (targetClass == null) { - ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter); - targetClass = (Class) resolvableType.resolve(); - } - HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null); Object body = NO_VALUE; diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java index b77fe1fe76..c5dc869bdf 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java @@ -176,6 +176,15 @@ class RequestMappingInfoHandlerMappingTests { testHttpMediaTypeNotSupportedException(mapping, "/person/1.json"); } + @PathPatternsParameterizedTest // gh-28062 + void getHandlerMethodTypeNotSupportedWithParseError(TestRequestMappingInfoHandlerMapping mapping) { + MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/person/1"); + request.setContentType("This string"); + assertThatExceptionOfType(HttpMediaTypeNotSupportedException.class) + .isThrownBy(() -> mapping.getHandler(request)) + .satisfies(ex -> assertThat(ex.getSupportedMediaTypes()).containsExactly(MediaType.APPLICATION_XML)); + } + @PathPatternsParameterizedTest void getHandlerHttpOptions(TestRequestMappingInfoHandlerMapping mapping) throws Exception { testHttpOptions(mapping, "/foo", "GET,HEAD,OPTIONS", null);