Search code examples
micronaut

Create multiple HTTP clients with same interface


We need to be able to connect to multiple Elasticsearch servers. We have a simple Elasticsearch client, defined with the declarative approach of Micronaut.

However, being a multi-tenant environment, we need to be able to define many such clients. Each of these clients has obviously a different URL and needs to use a different HTTPFilter for authentication.

Micronaut being a compile-time focused framework, how can I dynamically create many such beans, defined by configuration options?

UPDATE: I see that the @Factory combined with @EachBeanannotation might be a promising way to do it, but the declarative HTTP client is an interface, not a concrete class. How can I instantiate such a client, based on the interface alone?

See https://docs.micronaut.io/latest/guide/index.html#eachBean


Solution

  • Create your declarative @Client interface normally except for 2 details :

    1. make it point to a url locally "/proxy-elastic"
    2. add to every declared methods a @Header("X-Elastic-Cluster") String cluster
    @Client("/proxy-elastic")
    public interface DeclarativeHttpClient {
        @Get("/connectors/{name}")
        void diplay(@Header(value = "X-Elastic-Cluster") String cluster, String name);
    }
    

    Then, create a ProxyFilter and mutate the HttpRequest based on the Header value (https://docs.micronaut.io/latest/guide/index.html#proxyClient)

    @Filter("/proxy-elastic/**")
    public class KafkaConnectClientProxy extends OncePerRequestHttpServerFilter {
        @Inject
        ProxyHttpClient client;
    
        @Override
        protected Publisher<MutableHttpResponse<?>> doFilterOnce(HttpRequest<?> request, ServerFilterChain chain) {
            // retrieve the config for this cluster
            String cluster = request.getHeaders().get("X-Elastic-Cluster");
            var config = getConfigForCluster(cluster);
            URI newURI = URI.create(config.url);
    
            // call kafka connect with proper URL and Auth
            return Publishers.map(client.proxy(
                    request.mutate()
                            .uri(b -> b
                                    .scheme(newURI.getScheme())
                                    .host(newURI.getHost())
                                    .port(newURI.getPort())
                                    .replacePath(StringUtils.prependUri(
                                            newURI.getPath(),
                                            request.getPath().substring("/proxy-elastic".length())
                                    ))
                            )
                            .basicAuth(config.basicAuthUsername, config.basicAuthPassword)
            ), response -> response.header("X-My-Response-Header", "YYY"));
        }
    }