I have to return customized error structures in the REST API I'm developping using springboot 2.0.4.RELEASE.
I developped the controller advice below that does the work ... except when a client sends a request that contains illegal characters. For example: http://mycompany/myapp/myservice?fields=v1[*]
([*]
should have been url encoded).
First time a client sends such a request, I got this message into the log
2018-09-11 10:41:44.986 INFO 11464 --- [io-8080-exec-10] o.apache.coyote.http11.Http11Processor : Error parsing HTTP request header Note: further occurrences of HTTP header parsing errors will be logged at DEBUG level.
java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986 at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:479) ~[tomcat-embed-core-8.5.32.jar:8.5.32] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:684) ~[tomcat-embed-core-8.5.32.jar:8.5.32] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.32.jar:8.5.32] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:800) [tomcat-embed-core-8.5.32.jar:8.5.32] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1471) [tomcat-embed-core-8.5.32.jar:8.5.32] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.32.jar:8.5.32] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_144] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_144] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.32.jar:8.5.32] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_144]
... but none of my controller advice's method are called and I get an http error 400 (that's fine), with an empty body (that's wrong). Following time, same except nothing is ouput to log.
What should I do to catch this error and return my custom error structure?
Here is the (simplified code of my Controller advice):
package com.renault.api.examples.springboot;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
@ControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler {
public static final class CustomError {
// custom attributes removed for publication
}
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandler.class);
private static final String UNEXPECTED_EXCEPTION_MESSAGE = "An unexpected exception occurred";
@Override
protected ResponseEntity<Object> handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
System.out.println("handleTypeMismatch");
return new ResponseEntity<Object>(new CustomError(), headers, status);
}
@Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
System.out.println("handleHttpRequestMethodNotSupported");
return super.handleHttpRequestMethodNotSupported(ex, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
System.out.println("handleHttpMediaTypeNotSupported");
return super.handleHttpMediaTypeNotSupported(ex, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleHttpMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
System.out.println("handleHttpMediaTypeNotAcceptable");
return super.handleHttpMediaTypeNotAcceptable(ex, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleMissingPathVariable(MissingPathVariableException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
System.out.println("handleMissingPathVariable");
return super.handleMissingPathVariable(ex, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
System.out.println("handleMissingServletRequestParameter");
return super.handleMissingServletRequestParameter(ex, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
System.out.println("handleServletRequestBindingException");
return super.handleServletRequestBindingException(ex, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleConversionNotSupported(ConversionNotSupportedException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
System.out.println("handleConversionNotSupported");
return super.handleConversionNotSupported(ex, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
System.out.println("handleHttpMessageNotReadable");
return super.handleHttpMessageNotReadable(ex, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleHttpMessageNotWritable(HttpMessageNotWritableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
System.out.println("handleHttpMessageNotWritable");
return super.handleHttpMessageNotWritable(ex, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
System.out.println("handleMethodArgumentNotValid");
return super.handleMethodArgumentNotValid(ex, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleMissingServletRequestPart(MissingServletRequestPartException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
System.out.println("handleMissingServletRequestPart");
return super.handleMissingServletRequestPart(ex, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status,
WebRequest request) {
System.out.println("handleBindException");
return super.handleBindException(ex, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
System.out.println("handleNoHandlerFoundException");
return super.handleNoHandlerFoundException(ex, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleAsyncRequestTimeoutException(AsyncRequestTimeoutException ex,
HttpHeaders headers, HttpStatus status, WebRequest webRequest) {
System.out.println("handleAsyncRequestTimeoutException");
return super.handleAsyncRequestTimeoutException(ex, headers, status, webRequest);
}
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers,
HttpStatus status, WebRequest request) {
System.out.println("handleExceptionInternal");
return super.handleExceptionInternal(ex, body, headers, status, request);
}
/** Handles all exceptions not processed by spring boot thrown during request processing.
* <br>this means the errors like "path not found", "wrong argument" are not processed by this method.
* @param ex The exception that occurs.
* @param request The request that sends the exception.
* @return The response to send to client. For publication, all exceptions are considered unexpected => http error 500
*/
@org.springframework.web.bind.annotation.ExceptionHandler(value= {Exception.class})
protected ResponseEntity<Object> handle(Exception ex, WebRequest request) {
LOGGER.error(UNEXPECTED_EXCEPTION_MESSAGE, ex);
CustomError response = new CustomError();
// Fill custom error attributes ... removed for publication
return handleExceptionInternal(ex, response, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
}
/** Handles "standard" errors, like "path not found".
* @return an ErrorAttributes object describing the attributes of the response's body.
*/
@Bean
public ErrorAttributes errorAttributes() {
return new DefaultErrorAttributes() {
@Override
public int getOrder() {
System.out.println("getOrder is called");
return super.getOrder();
}
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
System.out.println("resolveException is called");
return super.resolveException(request, response, handler, ex);
}
@Override
public Throwable getError(WebRequest webRequest) {
System.out.println("getError is called");
return super.getError(webRequest);
}
@Override
public Map<String, Object> getErrorAttributes(WebRequest request, boolean includeStackTrace) {
System.out.println("getErrorAttributes is called");
CustomError err = new CustomError();
// Reuse default attributes to fill custom error ... removed for publication
@SuppressWarnings("unchecked")
Map<String, Object> map = OBJECT_MAPPER.convertValue(err, Map.class);
return map;
}
};
}
}
Thanks