spring-cloud / spring-cloud-gateway

An API Gateway built on Spring Framework and Spring Boot providing routing and more.
http://cloud.spring.io
Apache License 2.0
4.53k stars 3.33k forks source link

Spring Cloud Gateway MVC, order of before and after functions. #3387

Closed gatewaynovice closed 4 months ago

gatewaynovice commented 6 months ago

Question

I'm using Spring Cloud Gateway MVC to create custom routes. I need to associate multiple before functions and multiple after functions for each route I build. I needed to control the order of execution of these before and after function. I was assuming the order of functions in the route definition would also be the execution order of these functions. In practice during my testing I'm seeing the "before" functions are execution follow the order they are defined in but the "after functions execution is in the reverser order.

Is there anything I can do to control the order?

Example:

route(routeId)
        .nest(
            path("customPath/*"),
            () ->
                route()
                    .before(beforeFunction1)
                    .before(beforeFunction2)
                    .before(beforeFunction3)
                    .route(all(), routeHandlerFunction)
                    .after(afterFunction1)
                    .after(afterFunction2)
                    .after(afterFunction3)
                    .build())
        .onError(Throwable -> true, exceptionHandlerFunction)
        .build();

The order of execution I'm seeing is:

beforeFunction1 -> beforeFunction2 -> beforeFunction3 -> routeHandlerFunction -> afterFunction3 -> afterFunction2 -> afterFunction1

If there is a fixed order to expect I can define my route based on that. Is there documentation about this anywhere?

Thanks, Gotham

spencergibb commented 6 months ago

Your finding are correct about the order. Let's use this to document that fact. Before and after functions are special cases of HandlerFilterFunction, which has both before and after. So the more accurate execution is this

beforeFunction1 -> beforeFunction2 -> beforeFunction3 -> afterFunction1 (no op) -> afterFunction2 (no op) -> afterFunction3 (no op) -> 

routeHandlerFunction -> 

afterFunction3 -> afterFunction2 -> afterFunction1 -> beforeFunction3 (no op) -> beforeFunction2 (no op) -> beforeFunction1 (no op) 

All the before parts are executed in the order defined, then all the afters are executed in the reverse order they are defined. The fact that you put the route() call in between the calls to before() and `after() has no bearing on the order.

gatewaynovice commented 6 months ago

Thanks that helps. Would the after() functions still run if there is an exception that is handled by the onError() functions?

spencergibb commented 6 months ago

I'm not positive. @poutsma could say with certainty.

spencergibb commented 6 months ago

Looking at the code, onError() is just a specialization of a handler filter function. Looking at the code it looks like they might still run. What is your experience? https://github.com/spring-projects/spring-framework/blob/8137cc95669690f3e4055d6ccf484e98a07b6703/spring-webmvc/src/main/java/org/springframework/web/servlet/function/HandlerFilterFunction.java#L106-L129

gatewaynovice commented 6 months ago

I'm not seeing the after() functions run after an exception has been handled in my example above, not sure if that is because I defined the onError() handler outside the nested route. The exception happens to be thrown by one of the before() functions in my testing. I had to define the onError() handler outside the nested route otherwise any exceptions being thrown were not getting handled.

poutsma commented 5 months ago

Thanks that helps. Would the after() functions still run if there is an exception that is handled by the onError() functions?

As you already found out, they don't.

I'm not seeing the after() functions run after an exception has been handled in my example above, not sure if that is because I defined the onError() handler outside the nested route. The exception happens to be thrown by one of the before() functions in my testing. I had to define the onError() handler outside the nested route otherwise any exceptions being thrown were not getting handled.

Filters—such as those registered with before, after, and onError—are scoped, and only apply to the routes in the same nesting.