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();
}
私たちはついにこれを機能させました[私の同僚の一人によるコード化]:-
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;
}
}
私は同じ問題を抱えており、より簡単な方法でそれを解決することができました
Filter 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でこれを行う手順は次のとおりです。
error
型のフィルターを追加し、zuul自体のSendErrorFilter
の前に実行させます。RequestContext
が実行されないように、例外に関連付けられたキーをSendErrorFilter
から必ず削除してください。RequestDispatcher
を使用して、要求をErrorController
に転送します-以下で説明します。AbstractErrorController
を拡張し、再度例外を再スローします((key、exception)で新しいエラーフィルターを実行するステップで追加し、RequestContext
コントローラー内)。これで、例外が@ControllerAdviceクラスでキャッチされます。
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;
}
}
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>