spring WebFilter overrides ExceptionHandler header - java

i'm using both a WebFilter and a WebExceptionHandler.
The WebFilter should add a new header only if the ExceptionHandler didn't set it.
However, the WebFilter is added to the ServerWebExchange before the ExceptionHandler is executed by the WebHttpHandler, so it cannot tell whether the ExceptionHandler was triggered or not.
#Component
#Order(-2)
class MyErrorWebExceptionHandler(g: MyErrorAttributes, applicationContext: ApplicationContext, serverCodecConfigurer: ServerCodecConfigurer)
: AbstractErrorWebExceptionHandler(g, ResourceProperties(), applicationContext) {
init {
super.setMessageWriters(serverCodecConfigurer.writers)
super.setMessageReaders(serverCodecConfigurer.readers)
}
#Override
override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
return RouterFunctions.route(RequestPredicates.all(), HandlerFunction<ServerResponse> { renderErrorResponse(it) })
}
private fun renderErrorResponse(request: ServerRequest): Mono<ServerResponse> {
val errorPropertiesMap = getErrorAttributes(request, false)
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.headers { x ->
x.set(c_ApplicationStatus, errorPropertiesMap[c_ApplicationStatus].toString())
}.build()
}
#Component
class ServerResponseHeaderWebFilter : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
exchange.response.headers.set(c_ApplicationStatus, HttpStatus.OK.value().toString())
return chain.filter(exchange)
}
}

We can quickly model the execution order in this case, with something like:
WebFilter
|- setting the application status header to OK
|- calling chain.filter
|- finding the handler and calling it
|- in case of an error, the exception handler is called
|- after chain.filter
Once the filter chain is done with the exchange, the response has been committed and processed, so there's no way to change the response headers at that point. With this code sample, the exception handler, if executed, will override whatever header the web filter has set.
So technically, the answer to your question is that there's no way to modify the response once the handler chain has taken care of it. This is the expected behavior of Spring WebFlux, by design.
But it sounds like we need to take a step back and talk about what you're trying to achieve.
Are you trying to check whether your exception handler is being called at all while developing a feature?
Are you trying to adapt the error handling mechanism in Spring Boot in some way that's not possible right now?
If you'd like to talk about what you're trying to achieve, please ask another question so that SO users can benefit from this question.

eventually i found that the header set in the filter can be seen in the request.exchange() object. it must be removed there for the new header set to replace it.
private fun renderErrorResponse(request: ServerRequest): Mono<ServerResponse> {
val errorPropertiesMap = getErrorAttributes(request, false)
request.exchange().response.headers.remove(c_ApplicationStatus)
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.headers { x -> x.set(c_ApplicationStatus, value) }
.build()
}

Related

Spring Cloud Gateway - How To Modify Response Body In Post Filter

So far, I've not seen any solutions that is working for me. I've tried this and this.
Here is my custom filter:
#Component
public class TestFilter implements GlobalFilter, Ordered {
#Autowired
private ModifyResponseBodyGatewayFilterFactory modifyFilter;
#Autowired
private rewriteBody bodyRewrite;
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).map(ex -> {
GatewayFilter delegate = modifyFilter.apply(new ModifyResponseBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, bodyRewrite));
delegate.filter(exchange, chain);
return ex;
});
}
#Override
public int getOrder() {
return -1;
}
#Component
public class rewriteBody implements RewriteFunction<byte[], byte[]> {
#Override
public Publisher<byte[]> apply(ServerWebExchange exchange, byte[] body) {
byte[] newBody = "New response".getBytes();
return Mono.just(newBody);
}
}
}
The ModifyResponseBodyGatewayFilterFactory works if I implement it in a pre-filter, but how do I modify it in the post-filter.
To answer the question about modifying the response in post-filter. Firstly, need to understand the pre and post filters developed in Spring Cloud Gateway.
There is no specific separation for pre and post filters in Spring Cloud Gateway by any interface or any other component. It is simply 'How logic has been written for the same filter'.
If any logic written before chain.filter(exchange) method call are executed before running another 'filter in chain' or 'calling target service endpoint'. Since, the logic/code running before making call to another filter or target endpoint, it is called pre-filter and used for pre-processing like adding additional headers, security assertions, rate limiting and so on.
If any logic written after chain.filter(exchange) method call are executed after the processing completed in chain.filter(exchange) method, means the 'target service endpoint' has been completed and then the logic/lines written after chain.filter(exchange) is being executed. Therefore, it is called post-filter.
Since, it is just way of writing and placement of code decides whether it is for pre/post both can be written in single filter.
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return Mono.just(exchange)
.map(it -> {
it.getRequest().getHeaders().add("x-pre-header", "value");
return it;
})
.doOnNext(it -> {
logger.debug("Pre-Processing/PreFilter");
})
.map(it -> chain.filter(it))
.then()
.map(it -> {
exchange.getResponse().getHeaders().add("x-post-header", "value");
return it;
})
.doOnNext(it -> {
logger.debug("Post-Processing/PostFilter");
});
}
Additionally, sequence of execution of filters are controlled by ordering of filters.
Now, when question comes for modifying response body, it is very clear that response will be provided only when 'target service endpoint' called which requires chain.filter(exchange).
Here there is a twist, called 'response commit'. When response is already committed, cannot change in response body and as soon as chain.filter(exchange) is called, it will take micro/mili-seconds to write response to client and commit the response. Means, if any code written after then() method makes changes in response body it will throw exception 'response already committed'.
To avoid it, response body always modified while making chain.filter(exchange) call. Example, consider code written in ModifyResponseBodyGatewayFilterFactory's method filter(...) as:
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange.mutate()
.response(new ModifiedServerHttpResponse(exchange, config)).build());
}
Here, exchange is mutated and response body is set at the same moment. It will modify the response and invoke other filters in chain, if there is no any remaining filters in chain then it serve the response to client.
So conceptually, response body modification happens as post activity only it the filter comes later in chain. Needs like other filter should not be executed once response body modified / some specific filter needs to be executed after response body modification need to be managed by Filter's ordering.

How to check HTTP request header for certain endpoints in Spring Framework

I have a simple Spring Boot REST service for the IFTTT platform. Each authorized request will contain a header IFTTT-Service-Key with my account's service key and I will use that to either process the request or return a 401 (Unauthorized). However, I only want to do this for select endpoints -- and specifically not for ANY of the Spring actuator endpoints.
I have looked into Spring Security, using filters, using HandlerInterceptors, but none seem to fit what I am trying to do exactly. Spring security seems to come with a lot of extra stuff (especially the default user login), filters don't really seem to match the use case, and the handler interceptor works fine but I would have to code logic in to watch specific URLs and ignore others.
What is the best way to achieve what I am trying to do?
For reference, this is the code I have now:
public class ServiceKeyValidator implements HandlerInterceptor {
private final String myIftttServiceKey;
public ServiceKeyValidator(#Value("${ifttt.service-key}") String myIftttServiceKey) {
this.myIftttServiceKey = myIftttServiceKey;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// TODO will have to put logic in to skip this when actuator endpoints are added
String serviceKeyHeader = request.getHeader("IFTTT-Service-Key");
if (!myIftttServiceKey.equals(serviceKeyHeader)) {
var error = new Error("Incorrect value for IFTTT-Service-Key");
var errorResponse = new ErrorResponse(Collections.singletonList(error));
throw new UnauthorizedException(errorResponse);
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
You need to add filtering for the required endpoints in the place where you register your HandlerInterceptor.
For example:
#EnableWebMvc
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new ServiceKeyValidator())
.addPathPatterns("/ifttt/**")
.excludePathPatterns("/actuator/**");
}
}
You can use different URLs path matchers to filter which URL endpoints must be handled by your interceptor and which are not. As the method addPathPatterns returns InterceptorRegistration object that configures this.

Spring Boot - Interceptor: Exclude path only in GET request

I am working on a Java Spring project and we wrote an Interceptor for security. This class implements WebMvcConfigurer, and we override the addInterceptors method:
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SecurityInterceptor(aService, sService))
.excludePathPatterns("/", "/ping", "/resc")
}
This works nicely.
Now, the path "/resc" has a GET request but also a POST request. The point is the POST request must be intercepted and GET request to same path not.
Is there a way to achieve that?
Thanks
The InterceptorRegistration does not provide any methods for your purpose.
But I think there is a way to achive your behavior. You can autowire the ApplicationContext. And than do this inside your Interceptor:
try {
RequestMappingHandlerMapping req2HandlerMapping = (RequestMappingHandlerMapping)applicationContext.getBean("requestMappingHandlerMapping");
HandlerExecutionChain handlerExeChain = req2HandlerMapping.getHandler(request);
if (Objects.nonNull(handlerExeChain)) {
Method method = ((HandlerMethod) handlerExeChain.getHandler()).getMethod();
if (!method.isAnnotationPresent(GetMapping.class)) {
//Provide your Security Checks here.
}
} catch (Exception e) {
//provide some Error Code
}
If you also want to chekc a specific path. For example your "/resc" you can check that es well with an additional if.

Micronaut map empty Flowable to 404

Given: I have a service which produces a Flowable<T>. This Flowable<T> can be empty.
I have a controller, which looks similar to this:
#Controller("/api}")
class ApiController constructor( private val myService: MyService) {
#Get("/")
#Produces(MediaType.APPLICATION_JSON)
fun getSomething(): Flowable<T> {
return myService.get()
}
}
What I want to achieve: when the flowable is empty -> throw a HttpStatusException(404).
Otherwise return the flowable with the data inside.
What I already tried
I tried different combinations of the following RxJava Operators:
doOnError
onErrorResumeNext
onErrorReturn
switchIfEmpty
...
What I experienced
None of the options produced a 404 in the Browser/Postman.
A couple of options are just doing "nothing". Which means, that the page is not loading in the browser.
Other options are creating "OK" (200) responses with empty bodies.
And some are creating a CompositeException...
Does someone have a hint for me?
Update: as suggested:
#Controller("/api")
class HelloController {
#Get("/")
#Produces(MediaType.APPLICATION_JSON)
fun get(): Flowable<String> {
return Flowable.empty<String>()
.switchIfEmpty {
it.onError(HttpStatusException(HttpStatus.NOT_FOUND,""))
}
}
}
This produces the following, when I call it with firefox:
HttpResponseStatus: 200
HttpContent: [
Yes, the closing bracet is missing!
A possible Solution is, to use Maybe instead of Flowable.
#Controller("/api")
class HelloController {
#Get("/")
#Produces(MediaType.APPLICATION_JSON)
fun get(): Maybe<String> {
return Flowable.empty<String>()
.toList()
.flatMapMaybe { x ->
if (x.size == 0)
Maybe.empty<String>()
else
Maybe.just(x)
}
}
}
}
It is not the best solution, but a working one.
I don't know Micronaut, but I think this may be what you want:
Flowable.empty<Int>()
.switchIfEmpty { it.onError(Exception()) } // or HttpStatusException(404) in your case
.subscribe({
println(it)
}, {
it.printStackTrace()
})
The Flowable is empty, and what you get downstream is the Exception created inside switchIfEmpty. Note that you have to call it.onError inside switchIfEmpty.

what is the right way to handle errors in spring-webflux

I've been doing some research using spring-webflux and I like to understand what should be the right way to handle errors using Router Functions.
I've created an small project to test a couple of scenarios, and I like to get feedback about it, and see what other people is doing.
So far what I doing is.
Giving the following routing function:
#Component
public class HelloRouter {
#Bean
RouterFunction<?> helloRouterFunction() {
HelloHandler handler = new HelloHandler();
ErrorHandler error = new ErrorHandler();
return nest(path("/hello"),
nest(accept(APPLICATION_JSON),
route(GET("/"), handler::defaultHello)
.andRoute(POST("/"), handler::postHello)
.andRoute(GET("/{name}"), handler::getHello)
)).andOther(route(RequestPredicates.all(), error::notFound));
}
}
I've do this on my handler
class HelloHandler {
private ErrorHandler error;
private static final String DEFAULT_VALUE = "world";
HelloHandler() {
error = new ErrorHandler();
}
private Mono<ServerResponse> getResponse(String value) {
if (value.equals("")) {
return Mono.error(new InvalidParametersException("bad parameters"));
}
return ServerResponse.ok().body(Mono.just(new HelloResponse(value)), HelloResponse.class);
}
Mono<ServerResponse> defaultHello(ServerRequest request) {
return getResponse(DEFAULT_VALUE);
}
Mono<ServerResponse> getHello(ServerRequest request) {
return getResponse(request.pathVariable("name"));
}
Mono<ServerResponse> postHello(ServerRequest request) {
return request.bodyToMono(HelloRequest.class).flatMap(helloRequest -> getResponse(helloRequest.getName()))
.onErrorResume(error::badRequest);
}
}
Them my error handler do:
class ErrorHandler {
private static Logger logger = LoggerFactory.getLogger(ErrorHandler.class);
private static BiFunction<HttpStatus,String,Mono<ServerResponse>> response =
(status,value)-> ServerResponse.status(status).body(Mono.just(new ErrorResponse(value)),
ErrorResponse.class);
Mono<ServerResponse> notFound(ServerRequest request){
return response.apply(HttpStatus.NOT_FOUND, "not found");
}
Mono<ServerResponse> badRequest(Throwable error){
logger.error("error raised", error);
return response.apply(HttpStatus.BAD_REQUEST, error.getMessage());
}
}
Here is the full sample repo:
https://github.com/LearningByExample/reactive-ms-example
Spring 5 provides a WebHandler, and in the JavaDoc, there's the line:
Use HttpWebHandlerAdapter to adapt a WebHandler to an HttpHandler. The WebHttpHandlerBuilder provides a convenient way to do that while also optionally configuring one or more filters and/or exception handlers.
Currently, the official documentation suggests that we should wrap the router function into an HttpHandler before booting up any server:
HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);
With the help of WebHttpHandlerBuilder, we can configure custom exception handlers:
HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(toHttpHandler(routerFunction))
.prependExceptionHandler((serverWebExchange, exception) -> {
/* custom handling goes here */
return null;
}).build();
If you think, router functions are not the right place to handle exceptions, you throw HTTP Exceptions, that will result in the correct HTTP Error codes.
For Spring-Boot (also webflux) this is:
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
.
.
.
new ResponseStatusException(HttpStatus.NOT_FOUND, "Collection not found");})
spring securities AccessDeniedException will be handled correctly, too (403/401 response codes).
If you have a microservice, and want to use REST for it, this can be a good option, since those http exceptions are quite close to business logic, and should be placed near the business logic in this case. And since in a microservice you shouldn't have to much businesslogic and exceptions, it shouldn't clutter your code, too... (but of course, it all depends).
Why not do it the old fashioned way by throwing exceptions from handler functions and implementing your own WebExceptionHandler to catch 'em all:
#Component
class ExceptionHandler : WebExceptionHandler {
override fun handle(exchange: ServerWebExchange?, ex: Throwable?): Mono<Void> {
/* Handle different exceptions here */
when(ex!!) {
is NoSuchElementException -> exchange!!.response.statusCode = HttpStatus.NOT_FOUND
is Exception -> exchange!!.response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR
}
/* Do common thing like logging etc... */
return Mono.empty()
}
}
Above example is in Kotlin, since I just copy pasted it from a project I´m currently working on, and since the original question was not tagged for java anyway.
You can write a Global exception handler with custom response data and response code as follows. The code is in Kotlin. But you can convert it to java easily:
#Component
#Order(-2)
class GlobalWebExceptionHandler(
private val objectMapper: ObjectMapper
) : ErrorWebExceptionHandler {
override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> {
val response = when (ex) {
// buildIOExceptionMessage should build relevant exception message as a serialisable object
is IOException -> buildIOExceptionMessage(ex)
else -> buildExceptionMessage(ex)
}
// Or you can also set them inside while conditions
exchange.response.headers.contentType = MediaType.APPLICATION_PROBLEM_JSON
exchange.response.statusCode = HttpStatus.valueOf(response.status)
val bytes = objectMapper.writeValueAsBytes(response)
val buffer = exchange.response.bufferFactory().wrap(bytes)
return exchange.response.writeWith(Mono.just(buffer))
}
}
A quick way to map your exceptions to http response status is to throw org.springframework.web.server.ResponseStatusException / or create your own subclasses...
Full control over http response status + spring will add a response body with the option to add a reason.
In Kotlin it could look as simple as
#Component
class MyHandler(private val myRepository: MyRepository) {
fun getById(req: ServerRequest) = req.pathVariable("id").toMono()
.map { id -> uuidFromString(id) } // throws ResponseStatusException
.flatMap { id -> noteRepository.findById(id) }
.flatMap { entity -> ok().json().body(entity.toMono()) }
.switchIfEmpty(notFound().build()) // produces 404 if not found
}
fun uuidFromString(id: String?) = try { UUID.fromString(id) } catch (e: Throwable) { throw BadRequestStatusException(e.localizedMessage) }
class BadRequestStatusException(reason: String) : ResponseStatusException(HttpStatus.BAD_REQUEST, reason)
Response Body:
{
"timestamp": 1529138182607,
"path": "/api/notes/f7b.491bc-5c86-4fe6-9ad7-111",
"status": 400,
"error": "Bad Request",
"message": "For input string: \"f7b.491bc\""
}
What I am currently doing is simply providing a bean my WebExceptionHandler :
#Bean
#Order(0)
public WebExceptionHandler responseStatusExceptionHandler() {
return new MyWebExceptionHandler();
}
The advantage than creating the HttpHandler myself is that I have a better integration with WebFluxConfigurer if I provide my own ServerCodecConfigurer for example or using SpringSecurity

Categories