ASP.NET Core gRPC 源码解析

2024年4月3日 1224点热度 0人点赞 0条评论
内容纲要

整体逻辑

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 的配置过程:

file
file

接着,为每个服务的每个 Method 绑定路由地址和请求委托。

file

当请求的路由地址和 Method 区配时,就会进入对应的 RequestDeletgate 。

file

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 定义一致,因此可以直接被中间件使用。

file

不过要注意的是,ServerCallHandlerBase 在扫码 gRPC Service、绑定 Method 时已经创建实例了,也就是说每次请求相同的路由地址,对应的都是同一个 ServerCallHandlerBase 实例,只有 HandleCallAsync 方法会在每次调用是使用新的 HttpContext。

痴者工良

高级程序员劝退师

文章评论