Spring Cloud gateway send response in filter
Asked Answered
E

1

5

I am using spring cloud gateway as edge server. This is the flow

If request has a header named 'x-foo' then find the header value, get a string from another server and send that string as response instead of actually proxying the request.

Here is code for Filter DSL

@Bean
    public RouteLocator routes(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("foo-filter", r -> r.header('x-foo').and().header("x-intercepted").negate()
                        .filters(f -> f.filter(fooFilter))
                        .uri("http://localhost:8081")) // 8081 is self port, there are other proxy related configurations too
                .build();
    }

Code for Foo filter

@Component
@Slf4j
public class FooFilter implements GatewayFilter {

    @Autowired
    private ReactiveRedisOperations<String, String> redisOps;

    @Value("${header-name}")
    private String headerName;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        var foo = request.getHeaders().getFirst(headerName);

        return redisOps.opsForHash()
                .get("foo:" + foo, "response")
                .doOnSuccess(s -> {
                    log.info("data on success");
                    log.info(s.toString()); // I am getting proper response here
                    if (s != null) {
                        ServerHttpResponse response = exchange.getResponse();
                        response.setStatusCode(HttpStatus.OK);
                        response.getHeaders().set("x-intercepted", "true");

                        byte[] bytes = s.toString().getBytes(StandardCharsets.UTF_8);

                        DataBuffer buffer = response.bufferFactory().wrap(bytes);

                        response.writeWith(Mono.just(buffer));

                        response.setComplete();
                    }
                })
                .then(chain.filter(exchange));
    }

}

The problem is, the response has the response is getting proper 200 code, the injected header is present on response but the data is not available in response.

Edmiston answered 11/1, 2020 at 13:34 Comment(5)
This answer seems to be related to your question.Intercollegiate
Exactly what I needed.Edmiston
However this is giving me error after return. java.lang.UnsupportedOperationException: null at org.springframework.http.ReadOnlyHttpHeaders.putAll(ReadOnlyHttpHeaders.java:131) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): |_ checkpoint ? org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain] |_ checkpoint ? org.springframework.security.web.server.authorization.AuthorizationWebFilter [DefaultWebFilterChain]Edmiston
Hi @Akshay, Please share the updated code with stacktraceToffee
Apparently I had used switchIfEmpty and it was case early execution.Edmiston
E
6

This is how I got working.

  • Use flatMap instead of doOnSuccess
  • don't use then or switchIfEmpty instead use onErrorResume
  • Return the response.writeWith
    @Component
    @Slf4j
    public class FooFilter implements GatewayFilter {
    
        @Autowired
        private ReactiveRedisOperations<String, String> redisOps;
    
        @Value("${header-name}")
        private String headerName;
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            ServerHttpRequest request = exchange.getRequest();
            var foo = request.getHeaders().getFirst(headerName);
    
            return redisOps.opsForHash()
                    .get("foo:" + foo, "response")
                    .flatMap(s -> {
                        log.info("data on success");
                        log.info(s.toString()); // I am getting proper response here
                        if (s != null) {
                            ServerHttpResponse response = exchange.getResponse();
                            response.setStatusCode(HttpStatus.OK);
                            response.getHeaders().set("x-intercepted", "true");
    
                            byte[] bytes = s.toString().getBytes(StandardCharsets.UTF_8);
    
                            DataBuffer buffer = response.bufferFactory().wrap(bytes);
    
                            return response.writeWith(Mono.just(buffer));
   
                        }else{ return chain.filter(exchange).then(Mono.fromRunnable(() -> {log.info("It was empty")} }
                    })
                    .onErrorResume(chain.filter(exchange));
        }
    }
Edmiston answered 4/2, 2020 at 12:33 Comment(2)
Did this really return the response object?David
Yes, we are using it in ProdEdmiston

© 2022 - 2024 — McMap. All rights reserved.