如何使用鉴别器和声明性客户端路由请求?

How do I route a request using a discriminator and declarative client?

用例

我正在尝试使用 micronaut 声明式客户端调用服务。该服务实际上是许多相同的服务,但根据我们系统中的每个原则托管在不同的主机上。例如

来自 micronaut,我想使用一个请求 header X-tenetID,并根据它调用正确的服务。听起来很简单吧?

第一次尝试:使用过滤器

我尝试的第一件事是在客户端上使用过滤器

@FilterMatcher
@Documented
@Retention(RUNTIME)
@Target({TYPE, PARAMETER})
public @interface MyFilterAnnotation
{
}

@MyFilterAnnotation
@Client("http://replaceme.example.com/")
public interface MyClient
{
    @Post(uri = "some/endpoint")
    AuthResponse auth(@Body CustomCredentials credentials, @RequestAttribute(name = "tenet-id") String tenetID);
}

@MyFilterAnnotation
@Singleton
public class MyFilter implements HttpClientFilter
{
    @Override
    public int getOrder()
    {
        return -10; // Tried playing with the order here to no avail
    }

    @Override
    public Publisher<? extends HttpResponse<?>> doFilter(MutableHttpRequest<?> request, ClientFilterChain chain)
    {
        String tenetID = request.getAttribute("tenet-id", String.class).orElse(null);
        UriBuilder builder = UriBuilder.of(request.getUri());
        builder.host(tenetID + ".example.com");
        request.uri(builder.build());
        return chain.proceed(request);
    }
}

我已确认我的过滤器已被调用,请求 uri 已设置,但请求 uri 未在链的更下方得到尊重。覆盖订单似乎也没有任何效果。它仍在向 replaceme.example.com 发送流量。我是不是做错了什么?

我发现一些论坛帖子说这种方法可能行不通,因为选择的主机是在流程的早期完成的,以考虑 LoadBalancer 机制。这让我进行了第二次尝试:

第二次尝试:使用客户端 LoadBalancer

由于似乎无法在过滤器中更改 url 的主机,因此我尝试编写自己的 DiscoveryClientLoadBalancerFactory

@Replaces(DiscoveryClientLoadBalancerFactory.class)
public class MyLoadBalancer extends DiscoveryClientLoadBalancerFactory
{

    /**
     * @param discoveryClient The discover client
     */
    public MyLoadBalancer(final DiscoveryClient discoveryClient)
    {
        super(discoveryClient);
    }


    @Override
    public LoadBalancer create(final String serviceID)
    {
        return discriminator ->
        {
            // discriminator always seems to be null here.
            return Publishers.just(ServiceInstance.of("myService", discriminator + "example.com",8080));
        };       
    }
}

虽然我被困在这里,因为我不知道如何告诉声明性客户端使用我的 tenetID 作为鉴别器。我只见过 @Inject 注释创建了一个 DefaultHttpClient,它只调用 loadBalancer.select(getLoadBalancerDiscriminator())getLoadBalancerDiscriminator() 总是 returns null。有没有办法让我根据请求设置鉴别器 header?

什么不可行:应用程序配置

一些使用 bitbucket 的示例文档表明 url 来自配置。我们的原则来来去去,每项服务都必须使用存储在云保管库中的自己的凭据进行身份验证。因此,将某种地图存储在 application.conf 或类似位置在这里并不是很有用,因为我们不想每次添加新原则或轮换密钥时都必须重新启动。

帮助

在这两种方法之间,LoadBalancer 路由似乎更复杂,因为我正在做的是请求路由或 url 重写,而不是负载平衡。这些方法中的一种应该起作用吗?有没有更好的方法来做我想做的事?

我的最终解决方案是扩展 DefaultHttpClient,即使它被标记为 @Internal 并覆盖 resolveRequestURI 方法

public interface MyClient
{
    @Post(uri = "some/endpoint")
    AuthResponse auth(@Body CustomCredentials credentials, @Header("tenet-id") String tenetID);
}

@Singleton
public class MyHttpClient extends DefaultHttpClient
{
    // Takes the tenet-id header and looks up the url to send the request to.
    @Override
    protected <I> Publisher<URI> resolveRequestURI(final HttpRequest<I> request, final boolean includeContextPath)
    {
        var headers = (MutableHttpHeaders) request.getHeaders();
        var customerID = headers.get("tenet-id", String.class).orElseThrow(() -> new IllegalArgumentException("Request must have an tenet-id header"));
        var requestURI = request.getUri();
        var resolvedURI = secret.getUrl().resolve(includeContextPath ? prependContextPath(requestURI) : requestURI);
        return Publishers.just(resolvedURI);
    }
}

编辑

我了解到这个解决方案并不理想,因为它提供了一个具体的候选 bean 并阻止了任何类型的配置,就像您在普通 http 服务上所做的那样。租户传播之类的东西不再起作用。我必须解决这个问题。