整体逻辑
ASP.NET Core gRPC 的使用很简单,服务注册和中间件只有两行代码。
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddGrpc();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.MapGrpcService<GreeterService>();
注册服务:
public static IGrpcServerBuilder AddGrpc(this IServiceCollection services)
{
ArgumentNullThrowHelper.ThrowIfNull(services);
#if NET8_0_OR_GREATER
// Prefer AddRoutingCore when available.
// AddRoutingCore doesn't register a regex constraint and produces smaller result from trimming.
services.AddRoutingCore();
services.Configure<RouteOptions>(ConfigureRouting);
#else
services.AddRouting(ConfigureRouting);
#endif
services.AddOptions();
services.TryAddSingleton<GrpcMarkerService>();
services.TryAddSingleton(typeof(ServerCallHandlerFactory<>));
services.TryAddSingleton(typeof(IGrpcServiceActivator<>), typeof(DefaultGrpcServiceActivator<>));
services.TryAddSingleton(typeof(IGrpcInterceptorActivator<>), typeof(DefaultGrpcInterceptorActivator<>));
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<GrpcServiceOptions>, GrpcServiceOptionsSetup>());
// Model
services.TryAddSingleton<ServiceMethodsRegistry>();
services.TryAddSingleton(typeof(ServiceRouteBuilder<>));
services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IServiceMethodProvider<>), typeof(BinderServiceMethodProvider<>)));
return new GrpcServerBuilder(services);
}
GrpcMarkerService 是一个空服务,目的在于通过容器判断开发者是否注入了 gRPC 服务。
如果通过 .AddGrpc()
注册了服务,则一定可以获取到 GrpcMarkerService,反之,在初始化中间件的时候,可以直接判断处理,然后抛出异常。
每个 proto 文件都会通过 sg 技术生成一个类型,如 GreeterBase,每个生成的类型都会有两个 BindService 方法和 proto 自定义方法。
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
public static grpc::ServerServiceDefinition BindService(GreeterBase serviceImpl)
{
return grpc::ServerServiceDefinition.CreateBuilder()
.AddMethod(__Method_SayHello, serviceImpl.SayHello).Build();
}
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
public static void BindService(grpc::ServiceBinderBase serviceBinder, GreeterBase serviceImpl)
{
serviceBinder.AddMethod(__Method_SayHello, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::GrpcService1.HelloRequest, global::GrpcService1.HelloReply>(serviceImpl.SayHello));
}
这两个方法是动态生成的,会将 proto 中生成的方法名称生成 .AddMethod()
代码。
这样一来,中间件注册 gRPC 服务时,并不需要反射扫码服务类,而是直接调用 BindService 方法函数,直接配置好每个路由对应的 Method。
以下是每个 Method 的信息:
internal class MethodModel
{
public MethodModel(IMethod method, RoutePattern pattern, IList<object> metadata, RequestDelegate requestDelegate)
{
Method = method;
Pattern = pattern;
Metadata = metadata;
RequestDelegate = requestDelegate;
}
public IMethod Method { get; }
public RoutePattern Pattern { get; }
public IList<object> Metadata { get; }
public RequestDelegate RequestDelegate { get; }
}
以下是中间件注册一个 gRPC Service 的配置过程:
接着,为每个服务的每个 Method 绑定路由地址和请求委托。
当请求的路由地址和 Method 区配时,就会进入对应的 RequestDeletgate 。
Method 的生成,首先是在 BindService 中处理的。
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
public static void BindService(grpc::ServiceBinderBase serviceBinder, GreeterBase serviceImpl)
{
serviceBinder.AddMethod(__Method_SayHello, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::GrpcService1.HelloRequest, global::GrpcService1.HelloReply>(serviceImpl.SayHello));
}
// ProviderServiceBinder.cs
public override void AddMethod<TRequest, TResponse>(Method<TRequest, TResponse> method, UnaryServerMethod<TRequest, TResponse>? handler)
{
var (invoker, metadata) = CreateModelCore<UnaryServerMethod<TService, TRequest, TResponse>>(
method.Name,
new[] { typeof(TRequest), typeof(ServerCallContext) });
_context.AddUnaryMethod<TRequest, TResponse>(method, metadata, invoker);
}
public void AddUnaryMethod<TRequest, TResponse>(Method<TRequest, TResponse> method, IList<object> metadata, UnaryServerMethod<TService, TRequest, TResponse> invoker)
where TRequest : class
where TResponse : class
{
// ServerCallHandlerFactory.cs
var callHandler = _serverCallHandlerFactory.CreateUnary<TRequest, TResponse>(method, invoker);
AddMethod(method, RoutePatternFactory.Parse(method.FullName), metadata, callHandler.HandleCallAsync);
}
可以看到,然后通过层层的封装之后,将指定的 gRPC Service 的 Method 封装为一个 RequestDeletgate。
public delegate Task RequestDelegate(HttpContext context);
构建 RequestDelegate 完成后,处理请求的逻辑可以在 ServerCallHandlerBase.cs 中找到。
ServerCallHandlerBase 中的 HandleCallAsync 方法与 RequestDelegate 定义一致,因此可以直接被中间件使用。
不过要注意的是,ServerCallHandlerBase 在扫码 gRPC Service、绑定 Method 时已经创建实例了,也就是说每次请求相同的路由地址,对应的都是同一个 ServerCallHandlerBase 实例,只有 HandleCallAsync 方法会在每次调用是使用新的 HttpContext。
文章评论