第三十五节: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); } }
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); } }
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); } }
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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。