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:
-
Primero agregue un filtro de
error
de tipo y deje que se ejecute antes delSendErrorFilter
en zuul. -
Asegúrese de eliminar la clave asociada con la excepción del
RequestContext
para evitar que seSendErrorFilter
. -
Use
RequestDispatcher
para reenviar la solicitud alErrorController
, que se explica a continuación. -
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 desdeRequestContext
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>