GRpc异常处理Interceptors

全局错误处理服务端

微软已经实施了Interceptors,它们类似于FilterMiddlewares在ASP.NET MVC的核心或的WebAPI,它们可以用于全局异常处理,日志记录,验证等。
这是服务器端Interceptor自己的实现,Continuation是必须等待的Task,然后,如果引发了任何异常,则可以根据所获得的异常来控制RpcException和关联的StatusCode

using Grpc.Core;
using Grpc.Core.Interceptors;
using Microsoft.Extensions.Logging;
using System;
using System.Data.SqlClient;
using System.Threading.Tasks;

namespace DemoGrpc.Web.Logging
{
    public class LoggerInterceptor : Interceptor
    {
        private readonly ILogger<LoggerInterceptor> _logger;

        public LoggerInterceptor(ILogger<LoggerInterceptor> logger)
        {
            _logger = logger;
        }

        public async override Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
            TRequest request,
            ServerCallContext context,
            UnaryServerMethod<TRequest, TResponse> continuation)
        {
            LogCall(context);
            try
            {
                return await continuation(request, context);
            }
            catch (SqlException e)
            {
                _logger.LogError(e, $"An SQL error occured when calling {context.Method}");
                Status status;

                if (e.Number == -2)
                {
                    status = new Status(StatusCode.DeadlineExceeded, "SQL timeout");
                }
                else
                {
                    status = new Status(StatusCode.Internal, "SQL error");
                }
                throw new RpcException(status);
            }
            catch (Exception e)
            {
                _logger.LogError(e, $"An error occured when calling {context.Method}");
                throw new RpcException(Status.DefaultCancelled, e.Message);
            }
            
        }

        private void LogCall(ServerCallContext context)
        {
            var httpContext = context.GetHttpContext();
            _logger.LogDebug($"Starting call. Request: {httpContext.Request.Path}");
        }
    }
}

注册方式如下

using DemoGrpc.Web.Logging;
using DemoGrpc.Web.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace DemoGrpc.Web
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            //注册GRpc全局异常捕获
            services.AddGrpc(options =>
            {
                options.Interceptors.Add<LoggerInterceptor>();
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGrpcService<CountryGrpcService>();

                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
                });
            });
        }
    }
}

第二种方法也可以捕获到GRpc异常,但是写法比较粗糙。不推荐使用

using AutoMapper;
using DemoGrpc.Domain.Entities;
using DemoGrpc.Protobufs;
using DempGrpc.Services.Interfaces;
using Grpc.Core;
using System;
using System.Threading.Tasks;

public class CountryGrpcService : CountryService.CountryServiceBase
{
    private readonly ICountryService _countryService;
    private readonly IMapper _mapper;

    public CountryGrpcService(ICountryService countryService, IMapper mapper)
    {
        _countryService = countryService;
        _mapper = mapper;
    }

    public override async Task<CountriesReply> GetAll(EmptyRequest request, ServerCallContext context)
    {
        try
        {
            var countries = await _countryService.GetAsync();
            return _mapper.Map<CountriesReply>(countries);
        }
        catch (Exception e)
        {
            throw new RpcException(Status.DefaultCancelled, e.Message);
        }
    }
}

Rpc异常信息介绍如下

一个普通标题一个普通标题
Aborted操作被中止,通常是由于并发性问题,如顺序器检查失败、事务中止等。
AlreadyExists试图创建的一些实体(例如,文件或目录)已经存在。
Cancelled该操作被取消(通常由调用者取消)。
DataLoss不可恢复的数据丢失或损坏。
DeadlineExceeded操作完成前截止日期已过期。对于改变系统状态的操作,即使操作已经成功完成,也会返回此错误。例如,来自服务器的成功响应可能会延迟到截止日期过期。
FailedPrecondition操作被拒绝,因为系统没有处于执行操作所需的状态。例如,要删除的目录可能是非空的,一个rmdir操作被应用到一个非目录,等等。
Internal内部错误。表示底层系统期望的某些不变量被打破。
InvalidArgument客户端指定了无效的参数。注意,这与FAILED_PRECONDITION不同。INVALID_ARGUMENT表示与系统状态无关的参数(例如格式不正确的文件名)。
NotFound一些被请求的实体(例如,文件或目录)没有找到。
OK成功返回
OutOfRange操作尝试超过有效范围。例如,查找或读取文件的前端。
PermissionDenied调用者没有权限执行指定的操作。PERMISSION_DENIED不能用于由于耗尽某些资源而导致的拒绝(对于那些错误,应该使用RESOURCE_EXHAUSTED)。如果无法识别调用者,则不能使用PERMISSION_DENIED(对于那些错误,则使用UNAUTHENTICATED)。
ResourceExhausted某些资源已经耗尽,可能是每个用户的配额,或者可能是整个文件系统空间不足。
Unauthenticated未认证/授权
Unavailable该服务目前不可用。这很可能是一种暂时的情况,可以通过后退重新尝试来纠正。注意,重试非幂等操作并不总是安全的。
Unimplemented此服务中未实现或不支持/启用操作。
Unknown未知的错误。可能返回此错误的一个示例是,如果从另一个地址空间接收到的状态值属于此地址空间中未知的错误空间。如果api没有返回足够的错误信息,则可能会将其转换为此错误。

具体地址:https://grpc.github.io/grpc/csharp/api/Grpc.Core.StatusCode.html

RpcException有相对应的重载:具体如下,可以自定义异常返回的信息

全局错误处理客户端

客户端也可以通过拦截器处理错误(实现客户端事件,如AsyncUnaryCall):

using Grpc.Core;
using Grpc.Core.Interceptors;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

namespace ConsoleAppGRPC.Logging
{
    public class LoggerInterceptor : Interceptor
    {
        private readonly ILogger<LoggerInterceptor> _logger;

        public LoggerInterceptor(ILogger<LoggerInterceptor> logger)
        {
            _logger = logger;
        }

        public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
            TRequest request,
            ClientInterceptorContext<TRequest, TResponse> context,
            AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
        {
            LogCall(context.Method);

            var call = continuation(request, context);

            return new AsyncUnaryCall<TResponse>(HandleResponse(call.ResponseAsync), call.ResponseHeadersAsync, call.GetStatus, call.GetTrailers, call.Dispose);
        }

        private async Task<TResponse> HandleResponse<TResponse>(Task<TResponse> t)
        {
            try
            {
                var response = await t;
                _logger.LogDebug($"Response received: {response}");
                return response;
            }
            catch (RpcException ex)
            {
                _logger.LogError($"Call error: {ex.Message}");
                return default;
            }
        }

        private void LogCall<TRequest, TResponse>(Method<TRequest, TResponse> method) where TRequest : class where TResponse : class
        {
            _logger.LogDebug($"Starting call. Type: {method.Type}. Request: {typeof(TRequest)}. Response: {typeof(TResponse)}");
        }
    }
}

使用方法:

using DemoGrpc.Domain.Entities;
using DemoGrpc.Protobufs;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using System.Threading.Tasks;
using static DemoGrpc.Protobufs.CountryService;
using Microsoft.Extensions.Logging;
using ConsoleAppGRPC.Logging;

namespace ConsoleAppGRPC
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var services = new ServiceCollection();
            services.AddScoped<LoggerInterceptor>();
            services.AddLogging(logging =>
            {
                logging.AddConsole();
                logging.SetMinimumLevel(LogLevel.Debug);
            });

            services.AddGrpcClient<CountryServiceClient>(o =>
            {
                o.Address = new Uri("https://localhost:5001");
            }).AddInterceptor<LoggerInterceptor>();

            var provider = services.BuildServiceProvider();
            var client = provider.GetRequiredService<CountryServiceClient>();
            var logger = provider.GetRequiredService<ILogger<Program>>();

            var countries = (await client.GetAllAsync(new EmptyRequest()))?.Countries.Select(x => new Country
            {
                CountryId = x.Id,
                Description = x.Description,
                CountryName = x.Name
            }).ToList();

            if (countries != null)
            {
                logger.LogInformation("Found countries");
                countries.ForEach(x => Console.WriteLine($"Found country {x.CountryName} ({x.CountryId}) {x.Description}"));
            }
            else
            {
                logger.LogDebug("No countries found");
            }
        }
    }
}

得到的结果信息

结论
我们在本文中看到了如何全局处理错误。拦截器、RpcException、状态代码和返回信息的使用为我们提供了一定的灵活性,比如定制错误和向客户端发送相关错误的可能性。🙂

如果大家想要了解更多的Interceptors,请给我留言。

如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用 gRPC Java 进行通信时,我们需要考虑异常处理以确保我们的应用程序具有适当的容错能力。下面是一些常见的 gRPC 异常及其处理方法: 1. RpcException:这是一个通用的 gRPC 异常,表示 RPC 调用过程中出现了错误。可以通过捕获 RpcException 来处理 gRPC 的任何异常。例如: ```java try { // Make a gRPC call } catch (StatusRuntimeException e) { // Handle the exception } ``` 2. StatusRuntimeException:这是一个特殊的 RpcException 子类,表示 RPC 调用失败并返回了一个错误状态码。可以通过 getStatus() 方法来获取状态码并进行相应的处理。例如: ```java try { // Make a gRPC call } catch (StatusRuntimeException e) { if (e.getStatus().getCode() == Status.Code.NOT_FOUND) { // Handle the "not found" error } else if (e.getStatus().getCode() == Status.Code.PERMISSION_DENIED) { // Handle the "permission denied" error } else { // Handle any other errors } } ``` 3. DeadlineExceededException:这是一个表示 RPC 超时的异常。可以通过捕获 DeadlineExceededException 来处理超时异常。例如: ```java try { // Make a gRPC call } catch (DeadlineExceededException e) { // Handle the timeout error } ``` 4. CancellationException:这个异常表示 RPC 调用已被取消。可以通过捕获 CancellationException 来处理取消异常。例如: ```java try { // Make a gRPC call } catch (CancellationException e) { // Handle the cancellation error } ``` 以上是一些常见的 gRPC 异常及其处理方法,当然还有其他更详细的异常类型,可以在 gRPC Java 的官方文档中找到。在编写 gRPC 应用程序时,我们应该根据实际情况选择合适的异常类型并进行适当的处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值