Reputation: 393
What is the best (aka most efficient way) of implementing the management of unhappy paths with SCG Filters and Response with a body? Exception handling or response rewriting?
We are implementing an API Gateway with some complex GatewayFilters (between different type of Throttling and some other behaviours) that have in common an unhappy path where they terminate the exchange chain processing and should return immediately a 4xx response with a specific common body format (json/xml based on Accept header, we have legacy compatibility requirements).
My initial solution was to use the filter and then try to write the response with one of those options:
response.setStatusCode(..);
//set headers
return response.writeAndFlushWith(...);
or
response.setStatusCode(..);
//set headers
return response.writeWith(...);
or even
response.setStatusCode(..);
exchange.mutate().response(...);
return response.setComplete();
in each specific filter. But the processing of the body response seems quite a verbose process, plus the formatting of the error provided by the filter should not be a concern of the specific filter. We want a separation of concerns, so the filter should be defining headers, status and error message but someone else should transform that in the proper response format.
So after a long (and bloody :) ) discussion we come up with two possible options. Let's assume we have 4 Filters and our happy path would be (with some approximation non considering overlapping from global filters order):
(initial global filters) -> 1 -> 2 -> 3 -> 4-> 5 -> (other global filters) -> Routing -> (other global filters) -> 5 -> 4-> 3 -> 2 -> 1 -> 0 -> (initial global filters) -> Response
Implement a class java MyErrorHandler extends DefaultErrorWebExceptionHandler
and java MyErrorAttributes extends DefaultErrorAttributes
and that does the magic.
This means in the specific filter throwing an exception that extends ResponseStatusException
or is annotated with @ResponseStatus
and have it bubble out to the ExceptionHandler
This has the advantage of interrupting all the exchange processing in the filters, too, if I am not wrong.
Eg: given 3 filters and filter 3 throwing an exception we have
... 1 -> 2 -> 3 -> ExH -> (other global filters) -> Response
Add a filter (filter 0) in the topmost position, maybe one extending ModifyResponseGatewayFilter
that manages errors writing in the response by looking at a specific exchange attribute (eg:gateway.error
) for a bean and writes the response, and have the filter in the chain set that specific bean and generate the response with that.
This would have the following flow :
... -> 0 -> 1 -> 2 -> 3 -> 2 -> 1 -> 0 -> (other global filters) -> Response
Option
gateway.error
)The RedisRateLimiter offered by SCG uses option2 but does no body rewriting and uses response.setComplete()
but this doesn't allow any body rewriting later.
Is this the preferred approach?
Upvotes: 2
Views: 5660
Reputation: 249
you can handle Error, with one class which implements ErrorWebExceptionHandler
@Component
@Primary
@Slf4j
public class ErrorResponseFilter implements ErrorWebExceptionHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
...
return null;
}
}
and add this in another class
@Configuration
public class ExceptionConfig {
@Primary
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler errorWebExceptionHandler(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
ErrorResponseFilter jsonExceptionHandler = new ErrorResponseFilter();
jsonExceptionHandler.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList));
jsonExceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
jsonExceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
return jsonExceptionHandler;
}
}
Upvotes: 0
Reputation: 393
At the end what we went for to reduce unnecessary stacktrace creation was the following:
We overrode fillInStackTrace so not to call the native fillInStackTrace(int)
public synchronized Throwable fillInStackTrace() { return this; }
We stored the HttpHeaders we wanted to pass together with the HttpStatus
Upvotes: 1