grpc / grpc-dotnet

gRPC for .NET
Apache License 2.0
4.22k stars 777 forks source link

Header value duplication when double-registering? #2557

Open ricsiLT opened 1 month ago

ricsiLT commented 1 month ago

What version of gRPC and what language are you using?

Grpc.Net.ClientFactory 2.66.0 Google.Protobuf 3.25.2

What operating system (Linux, Windows,...) and version?

Windows

What runtime / compiler are you using (e.g. .NET Core SDK version dotnet --info)

.net8

What did you do?

If possible, provide a recipe for reproducing the error. Try being specific and include code snippets if helpful.

We have the following IServiceCollection extension methods:

AddWrappedStreamGrpcClient(IServiceCollection serviceCollection) {
    serviceCollection
        .AddGrpcClient<MyService.MyClient>()
        .AddCustomCallCredentials();
    serviceCollection.AddTransient<IStreamRpcWrapper, StreamRpcWrapper>
}
----
AddWrappedQeuryGrpcClient(IServiceCollection serviceCollection) {
    serviceCollection
        .AddGrpcClient<MyService.MyClient>()
        .AddCustomCallCredentials();
    serviceCollection.AddTransient<IQueryRpcWrapper, QueryRpcWrapper>();
}

Where both wrappers take MyService.MyClient into their constructor.

What did you expect to see?

When doing the following:

builder.Services.AddWrappedStreamGrpcClient().AddWrappedQeuryGrpcClient()

I'd expect both of my wrapper classes to get separate instances of grpc client, as far as the classes are concerned.

What did you see instead?

Due to double-instantiation, request header is somehow doubled. And not two headers, but two values in a single header:

Authorization: Bearer ey<...>, Bearer ey<...>

Anything else we should know about your project / environment?

.AddCustomCallCredentials() doesn't do anything outlandish, and I even tried to prevent it from adding metadata again:

        foreach (var entry in metadata)
        {
            if (entry.Key == "Authorization") return;
        }

        var token = (await tokenHelper.GetTokens()).AccessToken.Value;
        if (string.IsNullOrEmpty(token))
        {
            throw new ArgumentNullException(token);
        }

        metadata.Add("Authorization", $"Bearer {token}");
    }

I suppose I could redo the interface into:

AddSystemPrerequisites(); // register client + auth here
AddWrappedStreamGrpcClient();
AddWrappedQeuryGrpcClient();

but I was wondering if this behavior is intented.