我在 .net 4.6.2 上有一个使用
Grpc.Core
库的 .NET gRPC 客户端。
我已将 gRPC 通道注册为单例以按照[此建议]重用它。(https://learn.microsoft.com/en-us/aspnet/core/grpc/performance?view=aspnetcore-7.0#reuse- grpc 通道)
这就是我创建通道实例的方法:
public static InvokerAndChannel<T> CreateGrpcCallInvokerAndChannel<T>(
string endpoint, ChannelCredentials credentials, IServiceProvider serviceProvider)
where T : class
{
var licenseCode = serviceProvider.GetRequiredService<ILicenseCodeResolver>().GetLicenseCode();
var channel = new Channel(endpoint, credentials, new ChannelOption(ChannelOptions.MaxSendMessageLength, "1mb").Yield());
var callInvoker = channel
.Intercept(
new ClientTracingInterceptor(
new ClientTracingInterceptorOptions { RecordMessageEvents = false }))
.Intercept(new StaticMetadataInterceptor(licenseCode));
return new InvokerAndChannel<T>(callInvoker, channel);
}
如您所见,管道包括
StaticMetadataInterceptor
,它将许可证标头添加到调用中。实际上它也是一个单例。
我对同一 gRPC 服务的各种方法进行了多个并行调用。我看到
StaticMetadataInterceptor
收到相同的 ClientInterceptorContext
。至少 context.Options.Headers
不断增长,因此在多次调用后它会多次包含相同的标头。 为什么?
这是拦截器的代码:
internal sealed class StaticMetadataInterceptor : Interceptor
{
private readonly string _licenseCode;
public StaticMetadataInterceptor(string licenseCode)
{
_licenseCode = licenseCode;
}
private void ExtendMetadata<TRequest, TResponse>(ref ClientInterceptorContext<TRequest, TResponse> context)
where TRequest : class
where TResponse : class
{
var metadata = context.Options.Headers;
if (metadata == null)
{
metadata = new Metadata();
context = new ClientInterceptorContext<TRequest, TResponse>(context.Method, context. Host, context.Options.WithHeaders(metadata));
}
metadata.Add(GrpcStandardMetadataKeys.License, _licenseCode);
}
public override TResponse BlockingUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, BlockingUnaryCallContinuation<TRequest, TResponse> continuation)
{
ExtendMetadata(ref context);
return continuation(request, context);
}
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
ExtendMetadata(ref context);
return continuation(request, context);
}
public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation)
{
ExtendMetadata(ref context);
return continuation(context);
}
public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation)
{
ExtendMetadata(ref context);
return continuation(request, context);
}
public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncDuplexStreamingCallContinuation<TRequest, TResponse> continuation)
{
ExtendMetadata(ref context);
return continuation(context);
}
}
我找到了标题集合不断增长的原因。
生成的Grpc客户端有一个
Metada
类型的可选参数。我的假设是该集合中的项目将被复制,并且如果拦截器将标头添加到传出请求中,则初始元数据不会受到影响。这个假设是错误的。
示例:
var metadata = new Metadata();
client.CallFoo(request, metadata);
如果拦截器向请求添加标头,
metadata
将会改变。