Skip to content
Advertisement

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.

Advertisement

Answer

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.

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement