Tengo algunos problemas para obtener una solución completa para el manejo de errores. Creé una clase @ControllerAdvice que detecta Excepciones y QueryTimeException. Sin embargo, el mensaje 404 Not Found no va a mi página personalizada. Y HttpStatus.GATEWAY_TIMEOUT no se enruta correctamente. Sin embargo, HttpStatus.REQUEST_TIMEOUT y Exceptions se enrutan correctamente.
Aquí está mi controlador de errores. ¿Qué me estoy perdiendo o haciendo incorrectamente?
@ControllerAdvice public class MyErrorController implements ErrorController { public static final Logger logger = LoggerFactory.getLogger(MyErrorController.class); @ExceptionHandler(HttpServerErrorException.GatewayTimeout.class) @ResponseStatus(HttpStatus.GATEWAY_TIMEOUT) public String gatewayTimeout() { return "errors/error-timeout"; } @ExceptionHandler(QueryTimeoutException.class) @ResponseStatus(HttpStatus.REQUEST_TIMEOUT) public String requestTimeout() { return "errors/error-timeout"; } @ExceptionHandler(value = {HttpClientErrorException.class, Exception.class}) public String handleExceptions() { return "errors/error"; } @RequestMapping("/error") public String handleErrors(HttpServletRequest request, Exception ex) { return "errors/error"; } // This method is deprecated, so do not add the @Override annotation // However, the method must remain while the Spring team has not yet removed it from the ErrorController interface public String getErrorPath() { return "/error"; } }
He agregado lo siguiente a application.properties:
server.error.whitelabel.enabled=true server.error.path=/error
Para probar lo anterior, agregué los siguientes métodos a un controlador.
@GetMapping("/throw504") public ResponseEntity<Void> throwGatewayTimeout() { throw new HttpServerErrorException(HttpStatus.GATEWAY_TIMEOUT); } @GetMapping("/throw408") public ResponseEntity<Void> throwResponseTimeout() { throw new QueryTimeoutException(); } @GetMapping("/throw500") public ResponseEntity<Void> throwNotImplemented() { throw new HttpServerErrorException(HttpStatus.NOT_IMPLEMENTED); }
Así que pruebo ejecutando mi aplicación, navegando a localhost: 8080/throw408 (obtengo la página de tiempo de espera de error), localhost: 8080/throw500 (obtengo la página de error), que son correctos.
Luego pruebo localhost:8080/throw504 que abre incorrectamente la página de error, no el tiempo de espera de error como esperaba. Luego pruebo localhost:8080/jskld, que muestra una página 404 genérica, no la página de error que esperaba.
Analicemos esto un poco más, y probablemente verá el error en su código.
@ExceptionHandler(HttpServerErrorException.GatewayTimeout.class) @ResponseStatus(HttpStatus.GATEWAY_TIMEOUT) public String gatewayTimeout() { return "errors/error-timeout"; }
El controlador de excepciones espera una instancia de GatewayTimeout.class
para manejar esta excepción.
Sin embargo, cuando creas esta excepción en tu controlador
@GetMapping("/throw504") public ResponseEntity<Void> throwGatewayTimeout() { throw new HttpServerErrorException(HttpStatus.GATEWAY_TIMEOUT); <------ }
Si inspecciona esta clase encontrará
public class HttpServerErrorException extends HttpStatusCodeException { private static final long serialVersionUID = -2915754006618138282L; public HttpServerErrorException(HttpStatus statusCode) { super(statusCode); }
Entonces, el constructor que usa devuelve una HttpServerErrorException.class
No es una GatewayTimeout.class
GatewayTimeout.class
es una clase estática anidada que extiende HttpServerErrorException.class
.
public static final class GatewayTimeout extends HttpServerErrorException { private GatewayTimeout(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.GATEWAY_TIMEOUT, statusText, headers, body, charset); }
Si mira más de cerca en HttpServerErrorException.class
, hay un constructor que crea una instancia de GatewayTimeout.class
Una forma sería usar el constructor
HttpServerErrorException.create(HttpStatus.GATEWAY_TIMEOUT,"status text", new HttpHeaders(), new byte[] {}, Charset.defaultCharset());
Esto le devolverá una instancia de excepción de GatewayTimeout.class
que Spring probablemente podría igualar.
Por lo tanto, su controlador debe adaptarse para ser algo como
@GetMapping("/throw504") public ResponseEntity<Void> throwGatewayTimeout() { throw HttpServerErrorException.create(HttpStatus.GATEWAY_TIMEOUT,"status text", new HttpHeaders(), new byte[] {}, Charset.defaultCharset()); }
Al final, el punto es que GatewayTimeout.class
se extiende desde HttpServerErrorException
. Esto significa que GatewayTimeout.class
es una HttpServerErrorException
, pero una HttpServerErrorException
no sería con seguridad una GatewayTimeout.class
.