[SpringCloudGateway] ExceptionHandler
MVC에서는 @ControllerAdvice를 통해서 에러를 핸들링한다.
Reactive에서는 다른 방식의 에러 핸들링 방법을 제공하는데 이를 Spring Cloud Gateway에 어떻게 적용하는지 알아보자.
ErrorWebExceptionHandler
Spring cloud gateway에서 발생한 모든 예외를 처리할 수 있는 전역 핸들러 인터페이스
WebFlux의 최하단 필터 체인에서 동작하며, DispatcherHandler가 처리하지 못한 에러를 이 핸들러가 최종적으로 받는다.
에러 내역 로그를 남기거가, 에러 응답을 포맷팅 해서 주는 작업 등을 할 수 있다.
ErrorWebExceptionHandler에서는 handle을 구현해야 하는데
handle 메서드에서는 exchange에 response를 직접 핸들링해야 한다.
이러한 과정을 좀 더 간결하게 만들어준 추상클래스 AbstractErrorWebExceptionHandler가 존재
ErrorAttributes, ServerResponse 등을 활용하여 에러 내역 추출이나 응답 생성에 도움을 주는 기능을 제공
예시-ErrorWebExceptionHandler
exchange를 직접 핸들링 필요
@Component
@Order(-2)
public class CustomGlobalExceptionHandler implements ErrorWebExceptionHandler {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> errorDetails = Map.of(
"code", "500",
"message", ex.getMessage()
);
byte[] errorBytes;
try {
errorBytes = objectMapper.writeValueAsBytes(errorDetails);
} catch (JsonProcessingException e) {
errorBytes = "{\"code\": \"500\", \"message\": \"JSON Error\"}".getBytes(StandardCharsets.UTF_8);
}
DataBuffer buffer = response.bufferFactory().wrap(errorBytes);
return response.writeWith(Mono.just(buffer));
}
}
예시-AbstractErrorWebExceptionHandler
ErrorAttributes를 통해서 에러내용 추출 편리
ServerResponse를 사용해 간편하게 응답 생성 가능
getRoutingFunction() 메서드는 예외가 발생했을 때 어떻게 처리할지 라우터를 정의하는 곳
public class CustomExceptionHandler extends AbstractErrorWebExceptionHandler {
public CustomExceptionHandler(ErrorAttributes errorAttributes,
ApplicationContext applicationContext,
ServerCodecConfigurer configurer) {
super(errorAttributes, new DefaultErrorAttributes(), applicationContext);
setMessageReaders(configurer.getReaders());
setMessageWriters(configurer.getWriters());
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), request -> {
Map<String, Object> errorProps = getErrorAttributes(request, ErrorAttributeOptions.defaults());
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(errorProps);
});
}
}
Spring-boot의 기본 제공하는 auto-config를 확인해 보면
ErrorWebExceptionHandler 관련 빈이 등록되어있지 않다면
DefaultErrorWebExceptionHandler를 제공한다.
ErrorWebFluxAutoConfiguration.class
@Bean
@ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class, search = SearchStrategy.CURRENT)
@Order(-1)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes,
WebProperties webProperties, ObjectProvider<ViewResolver> viewResolvers,
ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext) {
DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(errorAttributes,
webProperties.getResources(), this.serverProperties.getError(), applicationContext);
exceptionHandler.setViewResolvers(viewResolvers.orderedStream().toList());
exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
return exceptionHandler;
}
DefaultErrorWebExceptionHandler의 코드를 살펴보면
만약 custom으로 exceptionHandler를 작성하고 싶은 경우 참고할 수 있을 것이다.
Map<String, Object> errorAttributes를 가져오는 코드에서는 기본적으로는 DefaultErrorAttributes가 사용되어
timestamp, path, status, error, requestId 만 포함되고
message, exception, trace를 추가 설정으로 넣을 수 있다.
server:
error:
include-exception: true
include-message: always
include-stacktrace: always
include-binding-errors: always