How to dynamically route an incoming request by using a URL->responsible-backend-service mapping service?

Viewed 2055

I am new to spring-cloud-gateway and I can't answer the question if I solved my problem the intended way. I hope someone can point me into the right direction, give advice or provide some exemplary sample code.

Requirement: An incoming request at my spring-cloud-gateway service shall be forwarded to the correct backend service (there are X of such backend services, each responsible for a specific task). The request itself does not contain enough information to decide which backend service to route to.

An additional REST-service exists that maps an arbitrary URL-to-responsible-backend-service-name. The response format is some small JSON, containing the name of the backend-service to forward to.

What would be the easiest/best/smartest/intended solution to implement this functionality with spring-cloud-gateway?

I tried implementing a GatewayFilter that first calls the mapping-service and depending on the result set GATEWAY_REQUEST_URL_ATTRfor the exchange. This works ok. But I have additional questions.

  1. Is it possible to omit the .uri("not://needed")) part in the route setup?

  2. Why does the order value need to be higher than 9999? (see code example)

@SpringBootApplication
@RestController
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public RouteLocator myRoutes(RouteLocatorBuilder builder) {
        return builder.routes().route(r -> r
                .alwaysTrue()
                .filters(f -> f.filter(new CustomRequestFilter()))
                .uri("not://needed"))  // how to omit ?
                .build();
    }


    public static class CustomRequestFilter implements GatewayFilter, Ordered {

        @Override
        public int getOrder() {
            return 10000; // why order > 9999 ?  (below 10000 does not work)
        }

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

            return getBackendServiceMappingResult(exchange.getRequest().getURI().toString()) //async call to REST service mapping URL->backend service name
                    .flatMap(mappingResult -> {
                        URI uri = mapServiceNameToBackendService(mappingResult.getServiceName());
                        exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, uri);
                        return chain.filter(exchange);
                    });
        }

        private URI mapServiceNameToBackendService(String serviceName) {
            try {
                switch (serviceName) {
                    case "serviceA": return new URI("http://httpbin.org:80/get");
                    case "serviceB": return new URI("https://someotherhost:443/");
                }
            } catch (URISyntaxException e) {
                //ignore
            }
            return null;
        }
    }

    static class MappingResult {
        String serviceName;
        public String getServiceName() {
            return serviceName;
        }
    }

    static Mono<MappingResult> getBackendServiceMappingResult(String uri) {
        WebClient client = WebClient.create("http://localhost:8080");
        return client.get().uri(uriBuilder -> uriBuilder.path("/mapping").queryParam("uri", uri).build()).retrieve().bodyToMono(MappingResult.class);
    }
}

Is there a better approach (with spring-cloud-gateway) to solve the requirement?

1 Answers

You can use this

 @Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
    return builder.routes().route(r ->
            r.alwaysTrue()
            .filters(f-> {
                f.changeRequestUri(serverWebExchange -> buildRequestUri(serverWebExchange, CustomRequestFilter));
                return f;
            })
            .uri(env.getProperty("birdeye.platform.url")))
            .build();

private Optional buildRequestUri(ServerWebExchange exchange,CustomRequestFilter customRequestFilter ){

    boolean updateuri = true;
    if(updateuri) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().pathWithinApplication().value();
        
        try {
             uriBiulder = UriComponentsBuilder.fromUri(new URI(backendUri)).path(backendPath)
    
        } catch (URISyntaxException e) {
        
            e.printStackTrace();
        }
    }else {
        uriBiulder = new URI("https://paytm.com:8080/category/create") 
    }
    return Optional.of(uriBiulder);
    
}
Related