第三十五节:gRPC拦截器、版本控制、安全性、日志集成

一. 拦截器

1. 工作原理

(1).流程:客户端发送信息 → 经过客户端拦截器 → 到达服务端拦截器 → 到达服务端方法。

如下图:

(2).实现:都要新建1个类, 实现Interceptors接口, 但对于客户端、服务端是 一元写法还是流式写法, 需要重写的方法不同哦

 A.一元写法:客户端重写AsyncUnaryCall方法, 服务端重写UnaryServerHandler方法

 B.单向流写法:客户端重写AsyncClientStreamingCall方法, 服务端重写ServerStreamingServerHandler方法

 C.双向流写法:客户端重写AsyncDuplexStreamingCall方法, 服务端重写DuplexStreamingServerHandler方法

详见下图表格:

2. 客户端拦截器(一元)

(1).控制台写法

 A.新建MyClientInterceptor1类,实现Interceptor接口,重写AsyncUnaryCall方法

代码分享:

 /// <summary>
    /// 客户端拦截器1
    /// </summary>
    public class MyClientInterceptor1:Interceptor
    {
        /// <summary>
        /// 重写AsyncUnaryCall,一元模式的拦截
        /// </summary>
        /// <returns></returns>
        public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
        {
            //服务于SayHello方法
            if (request is HelloRequest)
            {
                HelloRequest helloRequest = request as HelloRequest;
                helloRequest.UserName = $"[{ DateTime.Now}]:{helloRequest.UserName}";
            }
            //服务于CommitUserInfor方法
            if (request is UserInfor)
            {
                UserInfor uInfor = request as UserInfor;
                uInfor.UserName = $"[{ DateTime.Now}]:{uInfor.UserName}";
                uInfor.UserAge = $"[{ DateTime.Now}]:{uInfor.UserAge}";
                uInfor.UserAddress = $"[{ DateTime.Now}]:{uInfor.UserAddress}";
            }
            //添加表头信息
            var headers = context.Options.Headers;
            if (headers == null)
            {
                headers = new Metadata();
                var options = context.Options.WithHeaders(headers);
                context = new ClientInterceptorContext<TRequest, TResponse>(context.Method, context.Host, options);
            }
            headers.Add("caller-user", Environment.UserName);
            headers.Add("caller-machine", Environment.MachineName);
            headers.Add("caller-os", Environment.OSVersion.ToString());

            return base.AsyncUnaryCall(request, context, continuation);
        }
    }
View Code

 B.给指定客户添加过滤器: channel.Intercept(new MyClientInterceptor1());

             var client1 = new Greeter.GreeterClient(intercept);

代码分享:

         using var channel = GrpcChannel.ForAddress("https://localhost:5001");
                //添加拦截器
                var intercept = channel.Intercept(new MyClientInterceptor1());
                var client1 = new Greeter.GreeterClient(intercept);
                var reply = await client1.SayHelloAsync(new HelloRequest { UserName = "ypf" });
                Console.WriteLine("返回的消息为: " + reply.ReplyMsg);

(2).Core Mvc写法

 A.新建MyClientInterceptor1类,实现Interceptor接口,重写AsyncUnaryCall方法。

代码分享:

   /// <summary>
    /// 客户端拦截器1
    /// </summary>
    public class MyClientInterceptor1:Interceptor
    {
        /// <summary>
        /// 重写AsyncUnaryCall,一元模式的拦截
        /// </summary>
        /// <returns></returns>
        public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
        {
            //服务于SayHello方法
            if (request is HelloRequest)
            {
                HelloRequest helloRequest = request as HelloRequest;
                helloRequest.UserName =$"[{ DateTime.Now}]:{helloRequest.UserName}";
            }
            //服务于CommitUserInfor方法
            if (request is UserInfor)
            {
                UserInfor uInfor = request as UserInfor;
                uInfor.UserName = $"[{ DateTime.Now}]:{uInfor.UserName}";
                uInfor.UserAge = $"[{ DateTime.Now}]:{uInfor.UserAge}";
                uInfor.UserAddress = $"[{ DateTime.Now}]:{uInfor.UserAddress}";
            }
            //添加表头信息
            var headers = context.Options.Headers;
            if (headers == null)
            {
                headers = new Metadata();
                var options = context.Options.WithHeaders(headers);
                context = new ClientInterceptorContext<TRequest, TResponse>(context.Method, context.Host, options);
            }
            headers.Add("caller-user", Environment.UserName);
            headers.Add("caller-machine", Environment.MachineName);
            headers.Add("caller-os", Environment.OSVersion.ToString());

            return base.AsyncUnaryCall(request, context, continuation);
        }
    }
View Code

 B.给指定客户端添加过滤器,两种方式,详见ConfigureService

代码分享:

 public void ConfigureServices(IServiceCollection services)
  {
            services.AddControllersWithViews();

            //注册grpc指定客户端 + 添加拦截器模式1
            services.AddGrpcClient<GreeterClient>(o =>
            {
                o.Address = new Uri("https://localhost:5001");
            }).AddInterceptor(() => new MyClientInterceptor1());

            //注册grpc指定客户端 + 添加拦截器模式2
            //services.AddSingleton(new MyClientInterceptor1());
            //services.AddGrpcClient<GreeterClient>(o =>
            //{
            //    o.Address = new Uri("https://localhost:5001");
            //}).AddInterceptor<MyClientInterceptor1>(); 

    }

3. 服务端拦截器(一元)

 A.新建MyServerInterceptor1类,实现Interceptor接口,重写UnaryServerHandler方法

代码分享:

    /// <summary>
    /// 服务端拦截器1
    /// </summary>
    public class MyServerInterceptor1 : Interceptor
    {
        public override Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation)
        {
            //拦截到SayHello方法
            if (request is HelloRequest)
            {
                HelloRequest helloRequest = request as HelloRequest;
                Console.WriteLine($"【{DateTime.Now}】我是服务端拦截下来SayHello内容:{helloRequest.UserName}");
            }
            //拦截到CommitUserInfor方法
            if (request is UserInfor)
            {
                UserInfor uInfor = request as UserInfor;
                Console.WriteLine($"【{DateTime.Now}】我是服务端拦截下来SayHello内容:{uInfor.UserName},{uInfor.UserAge},{uInfor.UserAddress}");
            }
            //拦截到表头信息
            foreach (var header in context.RequestHeaders)
            {
                Console.WriteLine($"【{DateTime.Now}】我是服务端拦截表头的内容:{header.Key}:{header.Value}");
            }
            return base.UnaryServerHandler(request, context, continuation);
        }
    }
View Code

 B.在ConfigureService中可以全局拦截 和 单个服务拦截

代码分享:

        public void ConfigureServices(IServiceCollection services)
        {
            //注册gRPC服务 + 全局拦截
            services.AddGrpc(options =>
            {
                options.Interceptors.Add<MyServerInterceptor1>();
            });

            //注册gRPC服务 + 单个服务拦截
            //services.AddGrpc().AddServiceOptions<GreeterService>(options => {
            //    options.Interceptors.Add<MyServerInterceptor1>();
            //});

        }:

4. 测试

 分别把GrpcClient1和GrpcService1设置为一起启动、GrpcClient2和GrpcService1设置为一起启动,观察结果。

测试1:

测试2:

 

 

 

二. 其它

1. 版本控制

(1). package greet.v1;

(2). endpoints.MapGrpcService<GreeterServiceV1>();

   endpoints.MapGrpcService<GreeterServiceV2>();

2. 安全性

 gRPC 消息使用 HTTP/2 进行发送和接收,所以建议使用传输层安全性 (TLS) 以保护生产 gRPC 应用中的消息,gRPC 服务应仅侦听并响应受保护的端口,TLS 是在 Kestrel 中配置的。

详见 GrpcService1服务端中的Program程序

  public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    //TLS加密传输配置
                    webBuilder.ConfigureKestrel(serverOptions =>
                    {
                        serverOptions.ConfigureHttpsDefaults(listenOptions =>
                        {
                            listenOptions.SslProtocols = SslProtocols.Tls12;
                        });
                    });
                    webBuilder.UseStartup<Startup>();
                });

3. 日志

(1).说明

 gRPC 服务和 gRPC 客户端使用 .NET Core 日志记录编写日志。

(2).服务端日志

 由于 gRPC 服务托管在 ASP.NET Core 上,因此它使用 ASP.NET Core 日志记录系统。 在默认配置中,gRPC 只记录很少的信息,但这可以进行配置。  

在GrpcService1中Program类中进行配置,代码如下:

  public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                 //配置grpc日志级别
                 .ConfigureLogging(logging =>
                 {
                     logging.AddFilter("Grpc", LogLevel.Debug);
                 })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });

运行结果如下:

(3).客户端日志

A. 无DI

  安装【Microsoft.Extensions.Logging】【Microsoft.Extensions.Logging.Console】,代码和运行效果如下

         //日志
                var loggerFactory = LoggerFactory.Create(logging =>
                {
                    logging.AddConsole();
                    logging.SetMinimumLevel(LogLevel.Debug);
                });
                using var channel = GrpcChannel.ForAddress("https://localhost:5001",
                                                            new GrpcChannelOptions { LoggerFactory = loggerFactory });

                var client1 = new Greeter.GreeterClient(channel);
                var reply = await client1.SayHelloAsync(new HelloRequest { UserName = "ypf" });
                Console.WriteLine("返回的消息为: " + reply.ReplyMsg);

B. 有DI

  public class HomeController : Controller
    {
        public GreeterClient _client;
        private ILoggerFactory _loggerFactory;
        public HomeController(GreeterClient client, ILoggerFactory loggerFactory)
        {
            this._client = client;
            _loggerFactory = loggerFactory;
        }
        /// <summary>
        /// 客户端调用grpc方法
        /// </summary>
        /// <returns></returns>
        public async Task<IActionResult> Index()
        {
                var channel = GrpcChannel.ForAddress("https://localhost:5001",
                                     new GrpcChannelOptions { LoggerFactory = _loggerFactory });
                var _client = new Greeter.GreeterClient(channel);
                var reply = await _client.SayHelloAsync(new HelloRequest { UserName = "ypf" });
                ViewBag.msg1 = $"返回的消息为:{ reply.ReplyMsg}";
                var reply2 = await _client.CommitUserInforAsync(new UserInfor() { UserName = "ypf", UserAge = "20", UserAddress = "China" });
                ViewBag.msg2 = $"返回的消息为:status={reply2.Status},msg={reply2.Msg}";
            return View();
        }
    }

(4).日志指标

服务端指标:

客户端指标:

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
原文地址:https://www.cnblogs.com/yaopengfei/p/13379197.html