web-dev-qa-db-ja.com

Zuul例外のカスタマイズ

Zuulには、URLもルーティングされるサービスがダウンしているシナリオがあります。そのため、応答ボディは、JSONボディ応答に500 HTTPステータスとZuulExceptionがスローされます。

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

私がやりたいのは、JSON応答をカスタマイズまたは削除し、HTTPステータスコードを変更することだけです。

@ControllerAdviceを使用して例外ハンドラーを作成しようとしましたが、例外はハンドラーによって取得されません。

更新:

だから私はエラーが実行された後にrunメソッドに入ることを見ることができるZuul Filterを拡張しました。どうすれば応答を変更できますか?以下は私がこれまでに得たものです。 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();
}
17
Grinish Nepal

私たちはついにこれを機能させました[私の同僚の一人によるコード化]:-

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;
    }
}
23
Grinish Nepal

私は同じ問題を抱えており、より簡単な方法でそれを解決することができました

Filter run()メソッドにこれを入れるだけです

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

SendErrorFilterは、目的のstatusCodeを持つメッセージをユーザーに配信します。

例外パターン内のこの例外は、正確に見た目は良くありませんが、ここでは機能します。

6
Vasile Rotaru

多くの場合、転送はフィルターによって行われます。この場合、要求はコントローラーにも到達しません。これは、@ ControllerAdviceが機能しない理由を説明します。

コントローラで転送する場合は、@ ControllerAdviceが動作するはずです。 springが@ControllerAdviceアノテーションが付けられたクラスのインスタンスを作成するかどうかを確認します。そのためには、クラスにブレークポイントを配置し、ヒットするかどうかを確認します。

転送が発生するコントローラーメソッドにもブレークポイントを追加します。誤って、検査する以外のコントローラーメソッドを呼び出した可能性がありますか?

これらの手順は、問題の解決に役立つはずです。

@ControllerAdviceアノテーションが付けられたクラスに、@ ExceptionHandler(Exception.class)アノテーションが付けられたExceptionHandlerメソッドを追加します。これは、すべての例外をキャッチする必要があります。

EDIT:Zuulfilterによって返されたエラー応答を変換する独自のフィルターを追加することができます。そこで、必要に応じて応答を変更できます。

エラー応答をカスタマイズする方法については、ここで説明しています。

春のフィルターの例外処理

フィルターを正しく配置するのは少し難しいかもしれません。正しい位置については正確にはわかりませんが、フィルターの順序と例外を処理する場所に注意する必要があります。

Zuulfilterの前に配置する場合、doFilter()を呼び出した後にエラー処理をコーディングする必要があります。

Zuulfilterの後に配置する場合、doFilter()を呼び出す前にエラー処理をコーディングする必要があります。

DoFilter()の前後にフィルターにブレークポイントを追加すると、正しい位置を見つけるのに役立ちます。

@ControllerAdviceでこれを行う手順は次のとおりです。

  1. 最初にerror型のフィルターを追加し、zuul自体のSendErrorFilterの前に実行させます。
  2. RequestContextが実行されないように、例外に関連付けられたキーをSendErrorFilterから必ず削除してください。
  3. RequestDispatcherを使用して、要求をErrorControllerに転送します-以下で説明します。
  4. @RestControllerクラスを追加してAbstractErrorControllerを拡張し、再度例外を再スローします((key、exception)で新しいエラーフィルターを実行するステップで追加し、RequestContextコントローラー内)。

これで、例外が@ControllerAdviceクラスでキャッチされます。

3
Karim Masoud

Zuul RequestContextには、 この回答 で説明した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;
    }
}
3
xxxception
    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>
1
Ravi Gupta