java spring-boot spring-cloud microservices netflix-zuul

java - Personalizar la excepción de Zuul



spring-boot spring-cloud (6)

El Zuul RequestContext no contiene el error.exception como se menciona en esta respuesta .
Actualice el filtro de error de Zuul:

@Component public class ErrorFilter extends ZuulFilter { private static final Logger LOG = LoggerFactory.getLogger(ErrorFilter.class); private static final String FILTER_TYPE = "error"; private static final String THROWABLE_KEY = "throwable"; private static final int FILTER_ORDER = -1; @Override public String filterType() { return FILTER_TYPE; } @Override public int filterOrder() { return FILTER_ORDER; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { final RequestContext context = RequestContext.getCurrentContext(); final Object throwable = context.get(THROWABLE_KEY); if (throwable instanceof ZuulException) { final ZuulException zuulException = (ZuulException) throwable; LOG.error("Zuul failure detected: " + zuulException.getMessage()); // remove error code to prevent further error handling in follow up filters context.remove(THROWABLE_KEY); // populate context with new response values context.setResponseBody("Overriding Zuul Exception Body"); context.getResponse().setContentType("application/json"); // can set any error code as excepted context.setResponseStatusCode(503); } return null; } }

Tengo un escenario en Zuul donde el servicio al que se enruta la URL también podría estar inactivo. Entonces, el cuerpo de respuesta se arroja con 500 HTTP Status y ZuulException en la respuesta del cuerpo JSON.

{ "timestamp": 1459973637928, "status": 500, "error": "Internal Server Error", "exception": "com.netflix.zuul.exception.ZuulException", "message": "Forwarding error" }

Todo lo que quiero hacer es personalizar o eliminar la respuesta JSON y tal vez cambiar el código de estado HTTP.

Intenté crear un controlador de excepción con @ControllerAdvice pero el controlador no toma la excepción.

ACTUALIZACIONES:

Entonces extendí el filtro Zuul. Puedo verlo entrar en el método de ejecución después de que se haya ejecutado el error, ¿cómo puedo cambiar la respuesta? A continuación es lo que obtuve hasta ahora. Leí en alguna parte sobre SendErrorFilter, pero ¿cómo lo implemento y qué hace?

public class CustomFilter extends ZuulFilter { @Override public String filterType() { return "post"; } @Override public int filterOrder() { return 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { final RequestContext ctx = RequestContext.getCurrentContext(); final HttpServletResponse response = ctx.getResponse(); if (HttpStatus.INTERNAL_SERVER_ERROR.value() == ctx.getResponse().getStatus()) { try { response.sendError(404, "Error Error"); //trying to change the response will need to throw a JSON body. } catch (final IOException e) { e.printStackTrace(); } ; } return null; }

Se agregó esto a la clase que tiene @EnableZuulProxy

@Bean public CustomFilter customFilter() { return new CustomFilter(); }


El reenvío a menudo se realiza mediante un filtro, en este caso, la solicitud ni siquiera llega a un controlador. Esto explicaría por qué su @ControllerAdvice no funciona.

Si reenvía en el controlador, entonces @ControllerAdvice debería funcionar. Compruebe si spring crea una instancia de la clase anotada con @ControllerAdvice. Para ese lugar, un punto de interrupción en la clase y ver si se golpeó.

Agregue un punto de interrupción también en el método del controlador donde debe ocurrir el reenvío. ¿Puede usted invocar accidentalmente otro método de controlador del que inspecciona?

Estos pasos deberían ayudarlo a resolver el problema.

En su clase anotada con @ControllerAdvice agregue un método ExceptionHandler anotado con @ExceptionHandler (Exception.class), que debería capturar cada Excepción.

EDITAR: puede intentar agregar su propio filtro que convierta la respuesta de error devuelta por Zuulfilter. Allí puedes cambiar la respuesta como quieras.

Aquí se explica cómo se puede personalizar la respuesta de error:

manejo de excepción para filtro en primavera

Colocar el filtro correctamente puede ser un poco complicado. No estoy exactamente seguro de la posición correcta, pero debe tener en cuenta el orden de sus filtros y el lugar donde maneja la excepción.

Si lo coloca antes del Zuulfilter, debe codificar su manejo de errores después de llamar a doFilter ().

Si lo coloca después del Zuulfilter, debe codificar su manejo de errores antes de llamar a doFilter ().

Agregar puntos de interrupción en su filtro antes y después de doFilter () puede ayudar a encontrar la posición correcta.


Estos son los pasos para hacerlo con @ControllerAdvice:

  1. Primero agregue un filtro de error de tipo y deje que se ejecute antes del SendErrorFilter en zuul.
  2. Asegúrese de eliminar la clave asociada con la excepción del RequestContext para evitar que se SendErrorFilter .
  3. Use RequestDispatcher para reenviar la solicitud al ErrorController , que se explica a continuación.
  4. Agregue una clase @RestController y haga que extienda AbstractErrorController , y vuelva a lanzar la excepción nuevamente (agréguela en el paso de ejecutar su nuevo filtro de error con (clave, excepción), consígalo desde RequestContext en su controlador).

La excepción ahora quedará atrapada en su clase @ControllerAdvice.


Finalmente conseguimos que esto funcione [Codificado por uno de mi colega]:

public class CustomErrorFilter extends ZuulFilter { private static final Logger LOG = LoggerFactory.getLogger(CustomErrorFilter.class); @Override public String filterType() { return "post"; } @Override public int filterOrder() { return -1; // Needs to run before SendErrorFilter which has filterOrder == 0 } @Override public boolean shouldFilter() { // only forward to errorPath if it hasn''t been forwarded to already return RequestContext.getCurrentContext().containsKey("error.status_code"); } @Override public Object run() { try { RequestContext ctx = RequestContext.getCurrentContext(); Object e = ctx.get("error.exception"); if (e != null && e instanceof ZuulException) { ZuulException zuulException = (ZuulException)e; LOG.error("Zuul failure detected: " + zuulException.getMessage(), zuulException); // Remove error code to prevent further error handling in follow up filters ctx.remove("error.status_code"); // Populate context with new response values ctx.setResponseBody(“Overriding Zuul Exception Body”); ctx.getResponse().setContentType("application/json"); ctx.setResponseStatusCode(500); //Can set any error code as excepted } } catch (Exception ex) { LOG.error("Exception filtering in custom error filter", ex); ReflectionUtils.rethrowRuntimeException(ex); } return null; } }


Tuve el mismo problema y pude resolverlo de una manera más simple

Simplemente ponga esto en su método Filter run()

if (<your condition>) { ZuulException zuulException = new ZuulException("User message", statusCode, "Error Details message"); throw new ZuulRuntimeException(zuulException); }

y SendErrorFilter entregará al usuario el mensaje con el statusCode deseado.

Esta Excepción en un patrón de Excepción no se ve exactamente bien, pero funciona aquí.


The simplest solution is to follow first 4 steps. 1. Create your own CustomErrorController extends AbstractErrorController which will not allow the BasicErrorController to be called. 2. Customize according to your need refer below method from BasicErrorController. <pre><code> @RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = getStatus(request); return new ResponseEntity<>(body, status); } </pre></code> 4. You can control whether you want exception / stack trace to be printed or not can do as mentioned below: <pre><code> server.error.includeException=false server.error.includeStacktrace=ON_TRACE_PARAM </pre></code> ==================================================== 5. If you want all together different error response re-throw your custom exception from your CustomErrorController and implement the Advice class as mentioned below: <pre><code> @Controller @Slf4j public class CustomErrorController extends BasicErrorController { public CustomErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties, List<ErrorViewResolver> errorViewResolvers) { super(errorAttributes, serverProperties.getError(), errorViewResolvers); log.info("Created"); } @Override public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = getStatus(request); throw new CustomErrorException(String.valueOf(status.value()), status.getReasonPhrase(), body); } } @ControllerAdvice public class GenericExceptionHandler { // Exception handler annotation invokes a method when a specific exception // occurs. Here we have invoked Exception.class since we // don''t have a specific exception scenario. @ExceptionHandler(CustomException.class) @ResponseBody public ErrorListWsDTO customExceptionHandle( final HttpServletRequest request, final HttpServletResponse response, final CustomException exception) { LOG.info("Exception Handler invoked"); ErrorListWsDTO errorData = null; errorData = prepareResponse(response, exception); response.setStatus(Integer.parseInt(exception.getCode())); return errorData; } /** * Prepare error response for BAD Request * * @param response * @param exception * @return */ private ErrorListWsDTO prepareResponse(final HttpServletResponse response, final AbstractException exception) { final ErrorListWsDTO errorListData = new ErrorListWsDTO(); final List<ErrorWsDTO> errorList = new ArrayList<>(); response.setStatus(HttpStatus.BAD_REQUEST.value()); final ErrorWsDTO errorData = prepareErrorData("500", "FAILURE", exception.getCause().getMessage()); errorList.add(errorData); errorListData.setErrors(errorList); return errorListData; } /** * This method is used to prepare error data * * @param code * error code * @param status * status can be success or failure * @param exceptionMsg * message description * @return ErrorDTO */ private ErrorWsDTO prepareErrorData(final String code, final String status, final String exceptionMsg) { final ErrorWsDTO errorDTO = new ErrorWsDTO(); errorDTO.setReason(code); errorDTO.setType(status); errorDTO.setMessage(exceptionMsg); return errorDTO; } } </pre></code>