source

Zuul 예외 사용자 지정

lovecheck 2023. 2. 26. 09:51
반응형

Zuul 예외 사용자 지정

Zuul에서 URL이 라우팅되는 서비스도 다운될 수 있는 시나리오가 있습니다.그러면 응답 본문이 JSON 본문 응답에 500 HTTP Status 및 ZuulException으로 느려집니다.

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

JSON 응답을 커스터마이즈 또는 삭제하거나 HTTP 상태 코드를 변경하는 것 뿐입니다.

@ControllerAdvice를 사용하여 예외 핸들러를 생성하려고 했지만 핸들러가 예외를 포착하지 못했습니다.

갱신:

그래서 Zuul 필터를 확장했습니다.오류 실행 후 실행 메서드로 이행하는 것을 확인할 수 있습니다.그때 응답을 변경하는 방법은 무엇입니까?아래는 지금까지 제가 알아낸 것입니다.SendErrorFilter에 대해 어디서 읽었는데 어떻게 구현하고 어떤 기능을 합니까?

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;
    }

이것을 @EnableZuulProxy가 있는 클래스에 추가했습니다.

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

[동료 중 한 명이 코드화] 드디어 이 작업을 완료했습니다.-

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;
    }
}

Zuul Request Context에 포함되어 있지 않습니다.error.exception 답변에 언급된 바와 같이
최신 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;
    }
}

나도 같은 문제가 있어서 더 쉽게 해결할 수 있었다.

이 필터만 넣어주세요.run()방법

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

그리고.SendErrorFilter원하는 메시지를 사용자에게 전달합니다.statusCode.

예외 패턴의 이 예외는 보기 좋게 보이지는 않지만 여기서 작동합니다.

전송은 많은 경우 필터에 의해 이루어집니다.이 경우 요구는 컨트롤러에 도달하지 않습니다.이것이 @ControllerAdvice가 작동하지 않는 이유를 설명해 줍니다.

컨트롤러로 전송하면 @ControllerAdvice가 동작합니다.spring이 @ControllerAdvice에서 주석을 붙인 클래스의 인스턴스를 만들 수 있는지 확인합니다.그 때문에, 클래스에 브레이크 포인트를 배치해, 적중 여부를 확인합니다.

전송이 발생하는 컨트롤러 방식에도 브레이크 포인트를 추가합니다.검사 이외의 컨트롤러 메서드를 잘못 호출했을 가능성이 있습니다.

이러한 순서는, 문제의 해결에 도움이 됩니다.

@ControllerAdvice로 주석을 단 클래스에 @ExceptionHandler(Exception.class)로 주석을 단 ExceptionHandler 메서드를 추가합니다.이 메서드는 모든 예외를 검출해야 합니다.

EDIT : Zuulfilter에서 반환된 오류 응답을 변환하는 자체 필터를 추가할 수 있습니다.여기서 원하는 대로 응답을 변경할 수 있습니다.

에러 응답을 커스터마이즈 하는 방법에 대해서는, 다음과 같이 설명합니다.

스프링 필터 예외 처리

필터를 올바르게 설치하는 것은 조금 어려울 수 있습니다.올바른 위치에 대해서는 정확히 알 수 없지만 필터의 순서와 예외를 처리하는 위치를 알고 있어야 합니다.

Zuulfilter 앞에 배치하는 경우 doFilter()를 호출한 후 오류 처리를 코드화해야 합니다.

Zuulfilter 뒤에 배치할 경우 doFilter()를 호출하기 전에 오류 처리를 코드화해야 합니다.

doFilter() 전후로 필터에 브레이크 포인트를 추가하면 올바른 위치를 찾을 수 있습니다.

다음은 @ControllerAdvice를 사용하여 수행하는 절차입니다.

  1. '필터' 를 추가합니다.error.SendErrorFilter주울 그 자체.
  2. 에서 .RequestContext SendErrorFilter실행에서 제외됩니다.
  3. RequestDispatcherErrorController
  4. 하고 @RestController 클래스를 합니다.AbstractErrorController를 다시 필터를 합니다). (키, 예외로 (키, 예외로).RequestContext컨트롤러에 있습니다).

이것으로 예외가 @ControllerAdvice 클래스에서 검출됩니다.

    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>

이게 나한테 효과가 있었어.RestExceptionResponse는 @ControllerAdvice 내에서 사용되는 클래스이므로 내부 ZuulExceptions의 경우에도 동일한 예외 응답이 있습니다.

@Component
@Log4j
public class CustomZuulErrorFilter extends ZuulFilter {

    private static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran";

    @Override
    public String filterType() {
        return ERROR_TYPE;
    }

    @Override
    public int filterOrder() {
        return SEND_ERROR_FILTER_ORDER - 1; // Needs to run before SendErrorFilter which has filterOrder == 0
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        Throwable ex = ctx.getThrowable();
        return ex instanceof ZuulException && !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
    }

    @Override
    public Object run() {
        try {
            RequestContext ctx = RequestContext.getCurrentContext();
            ZuulException ex = (ZuulException) ctx.getThrowable();

            // log this as error
            log.error(StackTracer.toString(ex));

            String requestUri = ctx.containsKey(REQUEST_URI_KEY) ? ctx.get(REQUEST_URI_KEY).toString() : "/";
            RestExceptionResponse exceptionResponse = new RestExceptionResponse(HttpStatus.INTERNAL_SERVER_ERROR, ex, requestUri);

            // Populate context with new response values
            ctx.setResponseStatusCode(500);
            this.writeResponseBody(ctx.getResponse(), exceptionResponse);

            ctx.set(SEND_ERROR_FILTER_RAN, true);
        }
        catch (Exception ex) {
            log.error(StackTracer.toString(ex));
            ReflectionUtils.rethrowRuntimeException(ex);
        }
        return null;
    }


    private void writeResponseBody(HttpServletResponse response, Object body) throws IOException {
        response.setContentType("application/json");
        try (PrintWriter writer = response.getWriter()) {
            writer.println(new JSonSerializer().toJson(body));
        }
    }
}

출력은 다음과 같습니다.

{
    "timestamp": "2020-08-10 16:18:16.820",
    "status": 500,
    "error": "Internal Server Error",
    "path": "/service",
    "exception": {
        "message": "Filter threw Exception",
        "exceptionClass": "com.netflix.zuul.exception.ZuulException",
        "superClasses": [
            "com.netflix.zuul.exception.ZuulException",
            "java.lang.Exception",
            "java.lang.Throwable",
            "java.lang.Object"
        ],
        "stackTrace": null,
        "cause": {
            "message": "com.netflix.zuul.exception.ZuulException: Forwarding error",
            "exceptionClass": "org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException",
            "superClasses": [
                "org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException",
                "java.lang.RuntimeException",
                "java.lang.Exception",
                "java.lang.Throwable",
                "java.lang.Object"
            ],
            "stackTrace": null,
            "cause": {
                "message": "Forwarding error",
                "exceptionClass": "com.netflix.zuul.exception.ZuulException",
                "superClasses": [
                    "com.netflix.zuul.exception.ZuulException",
                    "java.lang.Exception",
                    "java.lang.Throwable",
                    "java.lang.Object"
                ],
                "stackTrace": null,
                "cause": {
                    "message": "Load balancer does not have available server for client: template-scalable-service",
                    "exceptionClass": "com.netflix.client.ClientException",
                    "superClasses": [
                        "com.netflix.client.ClientException",
                        "java.lang.Exception",
                        "java.lang.Throwable",
                        "java.lang.Object"
                    ],
                    "stackTrace": null,
                    "cause": null
                }
            }
        }
    }
}

언급URL : https://stackoverflow.com/questions/36461493/customizing-zuul-exception

반응형