Browse Source

ErrorResponse support in Spring MVC exception hierarchy

All Spring MVC exceptions from spring-web, now implement ErrorResponse
and expose HTTP error response information, including an RFC 7807 body.

See gh-27052
pull/28113/head
rstoyanchev 3 years ago
parent
commit
76be6373a8
  1. 12
      spring-web/src/main/java/org/springframework/web/HttpMediaTypeException.java
  2. 31
      spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotAcceptableException.java
  3. 57
      spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java
  4. 34
      spring-web/src/main/java/org/springframework/web/HttpRequestMethodNotSupportedException.java
  5. 28
      spring-web/src/main/java/org/springframework/web/bind/MethodArgumentNotValidException.java
  6. 3
      spring-web/src/main/java/org/springframework/web/bind/MissingMatrixVariableException.java
  7. 10
      spring-web/src/main/java/org/springframework/web/bind/MissingPathVariableException.java
  8. 3
      spring-web/src/main/java/org/springframework/web/bind/MissingRequestCookieException.java
  9. 3
      spring-web/src/main/java/org/springframework/web/bind/MissingRequestHeaderException.java
  10. 3
      spring-web/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java
  11. 21
      spring-web/src/main/java/org/springframework/web/bind/ServletRequestBindingException.java
  12. 18
      spring-web/src/main/java/org/springframework/web/context/request/async/AsyncRequestTimeoutException.java
  13. 1
      spring-web/src/main/java/org/springframework/web/multipart/support/MissingServletRequestPartException.java
  14. 4
      spring-webflux/src/main/java/org/springframework/web/reactive/DispatcherHandler.java
  15. 5
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java
  16. 4
      spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java
  17. 4
      spring-webflux/src/test/java/org/springframework/web/reactive/DispatcherHandlerErrorTests.java
  18. 23
      spring-webmvc/src/main/java/org/springframework/web/servlet/NoHandlerFoundException.java
  19. 4
      spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java
  20. 4
      spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequestBuilder.java
  21. 5
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java
  22. 2
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java

12
spring-web/src/main/java/org/springframework/web/HttpMediaTypeException.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,6 +22,7 @@ import java.util.List;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ProblemDetail;
/** /**
* Abstract base for exceptions related to media types. Adds a list of supported {@link MediaType MediaTypes}. * Abstract base for exceptions related to media types. Adds a list of supported {@link MediaType MediaTypes}.
@ -30,10 +31,12 @@ import org.springframework.http.MediaType;
* @since 3.0 * @since 3.0
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public abstract class HttpMediaTypeException extends ServletException { public abstract class HttpMediaTypeException extends ServletException implements ErrorResponse {
private final List<MediaType> supportedMediaTypes; private final List<MediaType> supportedMediaTypes;
private final ProblemDetail body = ProblemDetail.forRawStatusCode(getRawStatusCode());
/** /**
* Create a new HttpMediaTypeException. * Create a new HttpMediaTypeException.
@ -61,4 +64,9 @@ public abstract class HttpMediaTypeException extends ServletException {
return this.supportedMediaTypes; return this.supportedMediaTypes;
} }
@Override
public ProblemDetail getBody() {
return this.body;
}
} }

31
spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotAcceptableException.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,10 +18,14 @@ package org.springframework.web;
import java.util.List; import java.util.List;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
/** /**
* Exception thrown when the request handler cannot generate a response that is acceptable by the client. * Exception thrown when the request handler cannot generate a response that is
* acceptable by the client.
* *
* @author Arjen Poutsma * @author Arjen Poutsma
* @since 3.0 * @since 3.0
@ -30,11 +34,12 @@ import org.springframework.http.MediaType;
public class HttpMediaTypeNotAcceptableException extends HttpMediaTypeException { public class HttpMediaTypeNotAcceptableException extends HttpMediaTypeException {
/** /**
* Create a new HttpMediaTypeNotAcceptableException. * Constructor for when the {@code Accept} header cannot be parsed.
* @param message the exception message * @param message the parse error message
*/ */
public HttpMediaTypeNotAcceptableException(String message) { public HttpMediaTypeNotAcceptableException(String message) {
super(message); super(message);
getBody().setDetail("Could not parse Accept header");
} }
/** /**
@ -42,7 +47,23 @@ public class HttpMediaTypeNotAcceptableException extends HttpMediaTypeException
* @param supportedMediaTypes the list of supported media types * @param supportedMediaTypes the list of supported media types
*/ */
public HttpMediaTypeNotAcceptableException(List<MediaType> supportedMediaTypes) { public HttpMediaTypeNotAcceptableException(List<MediaType> supportedMediaTypes) {
super("Could not find acceptable representation", supportedMediaTypes); super("No acceptable representation", supportedMediaTypes);
}
@Override
public int getRawStatusCode() {
return HttpStatus.NOT_ACCEPTABLE.value();
}
@Override
public HttpHeaders getHeaders() {
if (CollectionUtils.isEmpty(getSupportedMediaTypes())) {
return HttpHeaders.EMPTY;
}
HttpHeaders headers = new HttpHeaders();
headers.setAccept(this.getSupportedMediaTypes());
return headers;
} }
} }

57
spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,14 +18,19 @@ package org.springframework.web;
import java.util.List; import java.util.List;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
/** /**
* Exception thrown when a client POSTs, PUTs, or PATCHes content of a type * Exception thrown when a client POSTs, PUTs, or PATCHes content of a type
* not supported by request handler. * not supported by request handler.
* *
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.0 * @since 3.0
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
@ -34,6 +39,9 @@ public class HttpMediaTypeNotSupportedException extends HttpMediaTypeException {
@Nullable @Nullable
private final MediaType contentType; private final MediaType contentType;
@Nullable
private final HttpMethod httpMethod;
/** /**
* Create a new HttpMediaTypeNotSupportedException. * Create a new HttpMediaTypeNotSupportedException.
@ -42,6 +50,8 @@ public class HttpMediaTypeNotSupportedException extends HttpMediaTypeException {
public HttpMediaTypeNotSupportedException(String message) { public HttpMediaTypeNotSupportedException(String message) {
super(message); super(message);
this.contentType = null; this.contentType = null;
this.httpMethod = null;
getBody().setDetail("Could not parse Content-Type");
} }
/** /**
@ -50,21 +60,38 @@ public class HttpMediaTypeNotSupportedException extends HttpMediaTypeException {
* @param supportedMediaTypes the list of supported media types * @param supportedMediaTypes the list of supported media types
*/ */
public HttpMediaTypeNotSupportedException(@Nullable MediaType contentType, List<MediaType> supportedMediaTypes) { public HttpMediaTypeNotSupportedException(@Nullable MediaType contentType, List<MediaType> supportedMediaTypes) {
this(contentType, supportedMediaTypes, "Content type '" + this(contentType, supportedMediaTypes, null);
(contentType != null ? contentType : "") + "' not supported");
} }
/** /**
* Create a new HttpMediaTypeNotSupportedException. * Create a new HttpMediaTypeNotSupportedException.
* @param contentType the unsupported content type * @param contentType the unsupported content type
* @param supportedMediaTypes the list of supported media types * @param supportedMediaTypes the list of supported media types
* @param msg the detail message * @param httpMethod the HTTP method of the request
* @since 6.0
*/ */
public HttpMediaTypeNotSupportedException(@Nullable MediaType contentType, public HttpMediaTypeNotSupportedException(@Nullable MediaType contentType,
List<MediaType> supportedMediaTypes, String msg) { List<MediaType> supportedMediaTypes, @Nullable HttpMethod httpMethod) {
this(contentType, supportedMediaTypes, httpMethod,
"Content-Type " + (contentType != null ? "'" + contentType + "' " : "") + "is not supported");
}
super(msg, supportedMediaTypes); /**
* Create a new HttpMediaTypeNotSupportedException.
* @param contentType the unsupported content type
* @param supportedMediaTypes the list of supported media types
* @param httpMethod the HTTP method of the request
* @param message the detail message
* @since 6.0
*/
public HttpMediaTypeNotSupportedException(@Nullable MediaType contentType,
List<MediaType> supportedMediaTypes, @Nullable HttpMethod httpMethod, String message) {
super(message, supportedMediaTypes);
this.contentType = contentType; this.contentType = contentType;
this.httpMethod = httpMethod;
getBody().setDetail("Content-Type " + this.contentType + " is not supported");
} }
@ -76,4 +103,22 @@ public class HttpMediaTypeNotSupportedException extends HttpMediaTypeException {
return this.contentType; return this.contentType;
} }
@Override
public int getRawStatusCode() {
return HttpStatus.UNSUPPORTED_MEDIA_TYPE.value();
}
@Override
public HttpHeaders getHeaders() {
if (CollectionUtils.isEmpty(getSupportedMediaTypes())) {
return HttpHeaders.EMPTY;
}
HttpHeaders headers = new HttpHeaders();
headers.setAccept(getSupportedMediaTypes());
if (HttpMethod.PATCH.equals(this.httpMethod)) {
headers.setAcceptPatch(getSupportedMediaTypes());
}
return headers;
}
} }

34
spring-web/src/main/java/org/springframework/web/HttpRequestMethodNotSupportedException.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2020 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,8 +22,12 @@ import java.util.Set;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -34,13 +38,15 @@ import org.springframework.util.StringUtils;
* @since 2.0 * @since 2.0
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class HttpRequestMethodNotSupportedException extends ServletException { public class HttpRequestMethodNotSupportedException extends ServletException implements ErrorResponse {
private final String method; private final String method;
@Nullable @Nullable
private final String[] supportedMethods; private final String[] supportedMethods;
private final ProblemDetail body;
/** /**
* Create a new HttpRequestMethodNotSupportedException. * Create a new HttpRequestMethodNotSupportedException.
@ -74,7 +80,7 @@ public class HttpRequestMethodNotSupportedException extends ServletException {
* @param supportedMethods the actually supported HTTP methods (may be {@code null}) * @param supportedMethods the actually supported HTTP methods (may be {@code null})
*/ */
public HttpRequestMethodNotSupportedException(String method, @Nullable String[] supportedMethods) { public HttpRequestMethodNotSupportedException(String method, @Nullable String[] supportedMethods) {
this(method, supportedMethods, "Request method '" + method + "' not supported"); this(method, supportedMethods, "Request method '" + method + "' is not supported");
} }
/** /**
@ -87,6 +93,8 @@ public class HttpRequestMethodNotSupportedException extends ServletException {
super(msg); super(msg);
this.method = method; this.method = method;
this.supportedMethods = supportedMethods; this.supportedMethods = supportedMethods;
this.body = ProblemDetail.forRawStatusCode(getRawStatusCode())
.withDetail("Method '" + method + "' is not supported");
} }
@ -123,4 +131,24 @@ public class HttpRequestMethodNotSupportedException extends ServletException {
return supportedMethods; return supportedMethods;
} }
@Override
public int getRawStatusCode() {
return HttpStatus.METHOD_NOT_ALLOWED.value();
}
@Override
public HttpHeaders getHeaders() {
if (ObjectUtils.isEmpty(this.supportedMethods)) {
return HttpHeaders.EMPTY;
}
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ALLOW, StringUtils.arrayToDelimitedString(this.supportedMethods, ", "));
return headers;
}
@Override
public ProblemDetail getBody() {
return this.body;
}
} }

28
spring-web/src/main/java/org/springframework/web/bind/MethodArgumentNotValidException.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,9 +17,12 @@
package org.springframework.web.bind; package org.springframework.web.bind;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.validation.BindException; import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError; import org.springframework.validation.ObjectError;
import org.springframework.web.ErrorResponse;
/** /**
* Exception to be thrown when validation on an argument annotated with {@code @Valid} fails. * Exception to be thrown when validation on an argument annotated with {@code @Valid} fails.
@ -30,10 +33,12 @@ import org.springframework.validation.ObjectError;
* @since 3.1 * @since 3.1
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class MethodArgumentNotValidException extends BindException { public class MethodArgumentNotValidException extends BindException implements ErrorResponse {
private final MethodParameter parameter; private final MethodParameter parameter;
private final ProblemDetail body;
/** /**
* Constructor for {@link MethodArgumentNotValidException}. * Constructor for {@link MethodArgumentNotValidException}.
@ -43,9 +48,20 @@ public class MethodArgumentNotValidException extends BindException {
public MethodArgumentNotValidException(MethodParameter parameter, BindingResult bindingResult) { public MethodArgumentNotValidException(MethodParameter parameter, BindingResult bindingResult) {
super(bindingResult); super(bindingResult);
this.parameter = parameter; this.parameter = parameter;
this.body = ProblemDetail.forRawStatusCode(getRawStatusCode()).withDetail(initMessage(parameter));
} }
@Override
public int getRawStatusCode() {
return HttpStatus.BAD_REQUEST.value();
}
@Override
public ProblemDetail getBody() {
return this.body;
}
/** /**
* Return the method parameter that failed validation. * Return the method parameter that failed validation.
*/ */
@ -55,9 +71,13 @@ public class MethodArgumentNotValidException extends BindException {
@Override @Override
public String getMessage() { public String getMessage() {
return initMessage(this.parameter);
}
private String initMessage(MethodParameter parameter) {
StringBuilder sb = new StringBuilder("Validation failed for argument [") StringBuilder sb = new StringBuilder("Validation failed for argument [")
.append(this.parameter.getParameterIndex()).append("] in ") .append(parameter.getParameterIndex()).append("] in ")
.append(this.parameter.getExecutable().toGenericString()); .append(parameter.getExecutable().toGenericString());
BindingResult bindingResult = getBindingResult(); BindingResult bindingResult = getBindingResult();
if (bindingResult.getErrorCount() > 1) { if (bindingResult.getErrorCount() > 1) {
sb.append(" with ").append(bindingResult.getErrorCount()).append(" errors"); sb.append(" with ").append(bindingResult.getErrorCount()).append(" errors");

3
spring-web/src/main/java/org/springframework/web/bind/MissingMatrixVariableException.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -57,6 +57,7 @@ public class MissingMatrixVariableException extends MissingRequestValueException
super("", missingAfterConversion); super("", missingAfterConversion);
this.variableName = variableName; this.variableName = variableName;
this.parameter = parameter; this.parameter = parameter;
getBody().setDetail("Required path parameter '" + this.variableName + "' is not present");
} }

10
spring-web/src/main/java/org/springframework/web/bind/MissingPathVariableException.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.web.bind; package org.springframework.web.bind;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
/** /**
* {@link ServletRequestBindingException} subclass that indicates that a path * {@link ServletRequestBindingException} subclass that indicates that a path
@ -59,6 +60,7 @@ public class MissingPathVariableException extends MissingRequestValueException {
super("", missingAfterConversion); super("", missingAfterConversion);
this.variableName = variableName; this.variableName = variableName;
this.parameter = parameter; this.parameter = parameter;
getBody().setDetail("Required URI variable '" + this.variableName + "' is not present");
} }
@ -83,4 +85,10 @@ public class MissingPathVariableException extends MissingRequestValueException {
return this.parameter; return this.parameter;
} }
@Override
public int getRawStatusCode() {
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
} }

3
spring-web/src/main/java/org/springframework/web/bind/MissingRequestCookieException.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -57,6 +57,7 @@ public class MissingRequestCookieException extends MissingRequestValueException
super("", missingAfterConversion); super("", missingAfterConversion);
this.cookieName = cookieName; this.cookieName = cookieName;
this.parameter = parameter; this.parameter = parameter;
getBody().setDetail("Required cookie '" + this.cookieName + "' is not present");
} }

3
spring-web/src/main/java/org/springframework/web/bind/MissingRequestHeaderException.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -57,6 +57,7 @@ public class MissingRequestHeaderException extends MissingRequestValueException
super("", missingAfterConversion); super("", missingAfterConversion);
this.headerName = headerName; this.headerName = headerName;
this.parameter = parameter; this.parameter = parameter;
getBody().setDetail("Required header '" + this.headerName + "' is not present");
} }

3
spring-web/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -52,6 +52,7 @@ public class MissingServletRequestParameterException extends MissingRequestValue
super("", missingAfterConversion); super("", missingAfterConversion);
this.parameterName = parameterName; this.parameterName = parameterName;
this.parameterType = parameterType; this.parameterType = parameterType;
getBody().setDetail("Required parameter '" + this.parameterName + "' is not present");
} }

21
spring-web/src/main/java/org/springframework/web/bind/ServletRequestBindingException.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +16,9 @@
package org.springframework.web.bind; package org.springframework.web.bind;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.ErrorResponse;
import org.springframework.web.util.NestedServletException; import org.springframework.web.util.NestedServletException;
/** /**
@ -30,7 +33,10 @@ import org.springframework.web.util.NestedServletException;
* @author Juergen Hoeller * @author Juergen Hoeller
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class ServletRequestBindingException extends NestedServletException { public class ServletRequestBindingException extends NestedServletException implements ErrorResponse {
private final ProblemDetail body = ProblemDetail.forRawStatusCode(getRawStatusCode());
/** /**
* Constructor for ServletRequestBindingException. * Constructor for ServletRequestBindingException.
@ -49,4 +55,15 @@ public class ServletRequestBindingException extends NestedServletException {
super(msg, cause); super(msg, cause);
} }
@Override
public int getRawStatusCode() {
return HttpStatus.BAD_REQUEST.value();
}
@Override
public ProblemDetail getBody() {
return this.body;
}
} }

18
spring-web/src/main/java/org/springframework/web/context/request/async/AsyncRequestTimeoutException.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2016 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +16,10 @@
package org.springframework.web.context.request.async; package org.springframework.web.context.request.async;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.ErrorResponse;
/** /**
* Exception to be thrown when an async request times out. * Exception to be thrown when an async request times out.
* Alternatively an applications can register a * Alternatively an applications can register a
@ -30,6 +34,16 @@ package org.springframework.web.context.request.async;
* @since 4.2.8 * @since 4.2.8
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class AsyncRequestTimeoutException extends RuntimeException { public class AsyncRequestTimeoutException extends RuntimeException implements ErrorResponse {
@Override
public int getRawStatusCode() {
return HttpStatus.SERVICE_UNAVAILABLE.value();
}
@Override
public ProblemDetail getBody() {
return ProblemDetail.forRawStatusCode(getRawStatusCode());
}
} }

1
spring-web/src/main/java/org/springframework/web/multipart/support/MissingServletRequestPartException.java

@ -42,6 +42,7 @@ public class MissingServletRequestPartException extends ServletRequestBindingExc
public MissingServletRequestPartException(String requestPartName) { public MissingServletRequestPartException(String requestPartName) {
super("Required request part '" + requestPartName + "' is not present"); super("Required request part '" + requestPartName + "' is not present");
this.requestPartName = requestPartName; this.requestPartName = requestPartName;
getBody().setDetail(getMessage());
} }

4
spring-webflux/src/main/java/org/springframework/web/reactive/DispatcherHandler.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -156,7 +156,7 @@ public class DispatcherHandler implements WebHandler, PreFlightRequestHandler, A
private <R> Mono<R> createNotFoundError() { private <R> Mono<R> createNotFoundError() {
return Mono.defer(() -> { return Mono.defer(() -> {
Exception ex = new ResponseStatusException(HttpStatus.NOT_FOUND, "No matching handler"); Exception ex = new ResponseStatusException(HttpStatus.NOT_FOUND);
return Mono.error(ex); return Mono.error(ex);
}); });
} }

5
spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -1261,8 +1261,7 @@ public abstract class RouterFunctions {
} }
private <R> Mono<R> createNotFoundError() { private <R> Mono<R> createNotFoundError() {
return Mono.defer(() -> Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND, return Mono.defer(() -> Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)));
"No matching router function")));
} }
private static <T> Mono<T> wrapException(Supplier<Mono<T>> supplier) { private static <T> Mono<T> wrapException(Supplier<Mono<T>> supplier) {

4
spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java

@ -200,8 +200,8 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
if (helper.hasParamsMismatch()) { if (helper.hasParamsMismatch()) {
throw new ServerWebInputException( throw new ServerWebInputException(
"Unsatisfied query parameter conditions: " + helper.getParamConditions() + "Expected parameters: " + helper.getParamConditions() +
", actual parameters: " + request.getQueryParams()); ", actual query parameters: " + request.getQueryParams());
} }
return null; return null;

4
spring-webflux/src/test/java/org/springframework/web/reactive/DispatcherHandlerErrorTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -84,7 +84,7 @@ public class DispatcherHandlerErrorTests {
StepVerifier.create(mono) StepVerifier.create(mono)
.consumeErrorWith(ex -> { .consumeErrorWith(ex -> {
assertThat(ex).isInstanceOf(ResponseStatusException.class); assertThat(ex).isInstanceOf(ResponseStatusException.class);
assertThat(ex.getMessage()).isEqualTo("404 NOT_FOUND \"No matching handler\""); assertThat(ex.getMessage()).isEqualTo("404 NOT_FOUND");
}) })
.verify(); .verify();

23
spring-webmvc/src/main/java/org/springframework/web/servlet/NoHandlerFoundException.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2015 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -21,10 +21,13 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.ErrorResponse;
/** /**
* By default when the DispatcherServlet can't find a handler for a request it * By default, when the DispatcherServlet can't find a handler for a request it
* sends a 404 response. However if its property "throwExceptionIfNoHandlerFound" * sends a 404 response. However, if its property "throwExceptionIfNoHandlerFound"
* is set to {@code true} this exception is raised and may be handled with * is set to {@code true} this exception is raised and may be handled with
* a configured HandlerExceptionResolver. * a configured HandlerExceptionResolver.
* *
@ -34,7 +37,7 @@ import org.springframework.http.HttpHeaders;
* @see DispatcherServlet#noHandlerFound(HttpServletRequest, HttpServletResponse) * @see DispatcherServlet#noHandlerFound(HttpServletRequest, HttpServletResponse)
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class NoHandlerFoundException extends ServletException { public class NoHandlerFoundException extends ServletException implements ErrorResponse {
private final String httpMethod; private final String httpMethod;
@ -42,6 +45,8 @@ public class NoHandlerFoundException extends ServletException {
private final HttpHeaders headers; private final HttpHeaders headers;
private final ProblemDetail detail = ProblemDetail.forRawStatusCode(getRawStatusCode());
/** /**
* Constructor for NoHandlerFoundException. * Constructor for NoHandlerFoundException.
@ -57,6 +62,11 @@ public class NoHandlerFoundException extends ServletException {
} }
@Override
public int getRawStatusCode() {
return HttpStatus.NOT_FOUND.value();
}
public String getHttpMethod() { public String getHttpMethod() {
return this.httpMethod; return this.httpMethod;
} }
@ -69,4 +79,9 @@ public class NoHandlerFoundException extends ServletException {
return this.headers; return this.headers;
} }
@Override
public ProblemDetail getBody() {
return this.detail;
}
} }

4
spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -206,7 +206,7 @@ class DefaultServerRequest implements ServerRequest {
return theConverter.read(clazz, this.serverHttpRequest); return theConverter.read(clazz, this.serverHttpRequest);
} }
} }
throw new HttpMediaTypeNotSupportedException(contentType, getSupportedMediaTypes(bodyClass)); throw new HttpMediaTypeNotSupportedException(contentType, getSupportedMediaTypes(bodyClass), method());
} }
private List<MediaType> getSupportedMediaTypes(Class<?> bodyClass) { private List<MediaType> getSupportedMediaTypes(Class<?> bodyClass) {

4
spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequestBuilder.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2020 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -313,7 +313,7 @@ class DefaultServerRequestBuilder implements ServerRequest.Builder {
return theConverter.read(clazz, inputMessage); return theConverter.read(clazz, inputMessage);
} }
} }
throw new HttpMediaTypeNotSupportedException(contentType, Collections.emptyList()); throw new HttpMediaTypeNotSupportedException(contentType, Collections.emptyList(), method());
} }
@Override @Override

5
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -264,7 +264,8 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
throw new HttpMediaTypeNotSupportedException(ex.getMessage()); throw new HttpMediaTypeNotSupportedException(ex.getMessage());
} }
} }
throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<>(mediaTypes)); throw new HttpMediaTypeNotSupportedException(
contentType, new ArrayList<>(mediaTypes), HttpMethod.valueOf(request.getMethod()));
} }
if (helper.hasProducesMismatch()) { if (helper.hasProducesMismatch()) {

2
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java

@ -206,7 +206,7 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
return null; return null;
} }
throw new HttpMediaTypeNotSupportedException(contentType, throw new HttpMediaTypeNotSupportedException(contentType,
getSupportedMediaTypes(targetClass != null ? targetClass : Object.class)); getSupportedMediaTypes(targetClass != null ? targetClass : Object.class), httpMethod);
} }
MediaType selectedContentType = contentType; MediaType selectedContentType = contentType;

Loading…
Cancel
Save