Share via


.NET 上的 gRPC 攔截器

作者:Ernest Nguyen

攔截器是一種 gRPC 概念,可讓應用程式與傳入或傳出的 gRPC 呼叫互動。 其可提供擴充要求處理管線的方式。

攔截器會針對通道或服務進行設定,並針對每個 gRPC 呼叫自動執行。 由於攔截器對於使用者的應用程式邏輯而言是透明的,因此它們是常見案例 (例如,記錄、監視、驗證及驗證) 的絕佳解決方案。

Interceptor 類型

藉由建立繼承自 Interceptor 類型的類別,可為 gRPC 伺服器和用戶端實作攔截器:

public class ExampleInterceptor : Interceptor
{
}

根據預設,Interceptor 基底類別不會執行任何動作。 透過覆寫攔截器實作中適當的基底類別方法,將行為新增至攔截器。

用戶端攔截器

gRPC 用戶端攔截器會攔截傳出的 RPC 叫用。 其提供對已傳送要求、傳入回應及用戶端呼叫內容的存取權。

要針對用戶端覆寫的 Interceptor 方法:

  • BlockingUnaryCall:攔截一元 RPC 的封鎖叫用。
  • AsyncUnaryCall:攔截一元 RPC 的非同步叫用。
  • AsyncClientStreamingCall:攔截用戶端串流 RPC 的非同步叫用。
  • AsyncServerStreamingCall:攔截伺服器串流 RPC 的非同步叫用。
  • AsyncDuplexStreamingCall:攔截雙向串流 RPC 的非同步叫用。

警告

雖然 BlockingUnaryCallAsyncUnaryCall 都是指一元 RPC,但二者無法互換。 AsyncUnaryCall 不會攔截封鎖叫用,而 BlockingUnaryCall 不會攔截非同步叫用。

建立用戶端 gRPC 攔截器

下列程式碼提供攔截一元呼叫之非同步叫用的基本範例:

public class ClientLoggingInterceptor : Interceptor
{
    private readonly ILogger _logger;

    public ClientLoggingInterceptor(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<ClientLoggingInterceptor>();
    }

    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
        TRequest request,
        ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
    {
        _logger.LogInformation("Starting call. Type/Method: {Type} / {Method}",
            context.Method.Type, context.Method.Name);
        return continuation(request, context);
    }
}

覆寫 AsyncUnaryCall

  • 攔截非同步一元呼叫。
  • 記錄呼叫的詳細資料。
  • 呼叫傳入方法的 continuation 參數。 如果這是最後一個攔截器,則會叫用鏈結中的下一個攔截器或基礎呼叫啟動程式。

對於每種服務方法,Interceptor 上的方法都有不同的簽章。 不過,continuationcontext 參數背後的概念保持不變:

  • continuation 是委派,可叫用鏈結中的下一個攔截器或基礎呼叫啟動程式 (如果鏈結中沒有任何攔截器)。 呼叫零次或多次並不是錯誤。 攔截器不需要傳回從 continuation 委派傳回的呼叫表示法 (如果是一元 RPC,則為 AsyncUnaryCall)。 如果省略委派呼叫並傳回您自己的呼叫表示法執行個體,則會中斷攔截器的鏈結,並立即傳回相關聯的回應。
  • context 包含與用戶端呼叫相關聯的範圍值。 請使用 context 來傳遞中繼資料,例如安全性主體、認證或追蹤資料。 此外,context 還包含期限和取消的相關資訊。 如需詳細資訊,請參閱具有期限和取消功能的可靠 gRPC 服務

在用戶端攔截器中等候回應

攔截器可以藉由更新 AsyncUnaryCall<TResponse>.ResponseAsyncAsyncClientStreamingCall<TRequest, TResponse>.ResponseAsync 值,等候一元和用戶端串流呼叫中的回應。

public class ErrorHandlerInterceptor : Interceptor
{
    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
        TRequest request,
        ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
    {
        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> inner)
    {
        try
        {
            return await inner;
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException("Custom error", ex);
        }
    }
}

上述 程式碼:

  • 建立覆寫 AsyncUnaryCall 的新攔截器。
  • 覆寫 AsyncUnaryCall
    • 呼叫 continuation 參數以叫用攔截器鏈結中的下一個項目。
    • 根據接續的結果建立新的 AsyncUnaryCall<TResponse> 執行個體。
    • 使用 HandleResponse 方法包裝 ResponseAsync 工作。
    • 使用 HandleResponse 等候回應。 等候回應允許在用戶端收到回應之後新增邏輯。 透過在 try-catch 區塊中等候回應,即可記錄來自呼叫的錯誤。

如需如何建立用戶端攔截器的詳細資訊,請參閱 grpc/grpc-dotnet GitHub 存放庫中的 ClientLoggerInterceptor.cs 範例

設定用戶端攔截器

gRPC 用戶端攔截器是在通道上設定。

下列程式碼範例:

  • 使用 GrpcChannel.ForAddress 建立通道。
  • 使用 Intercept 擴充方法將通道設定為使用攔截器。 請注意,這個方法會傳回 CallInvoker。 強型別 gRPC 用戶端可以從啟動程式 (就像通道一樣) 建立。
  • 從啟動程式建立用戶端。 用戶端所發出的 gRPC 呼叫會自動執行攔截器。
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var invoker = channel.Intercept(new ClientLoggerInterceptor());

var client = new Greeter.GreeterClient(invoker);

可以鏈結 Intercept 擴充方法,為通道設定多個攔截器。 或者,使用一個可接受多個攔截器的 Intercept 多載。 您可以針對單一 gRPC 呼叫執行任意數目的攔截器,如下列範例所示:

var invoker = channel
    .Intercept(new ClientTokenInterceptor())
    .Intercept(new ClientMonitoringInterceptor())
    .Intercept(new ClientLoggerInterceptor());

攔截器會依鏈結 Intercept 擴充方法的反向順序叫用。 在上述程式碼中,攔截器會依下列順序叫用:

  1. ClientLoggerInterceptor
  2. ClientMonitoringInterceptor
  3. ClientTokenInterceptor

如需如何使用 gRPC 用戶端處理站設定攔截器的資訊,請參閱 .NET 中的 gRPC 用戶端處理站整合

伺服器攔截器

gRPC 伺服器攔截器可攔截傳入的 RPC 要求。 其提供對傳入要求、傳出回應及伺服器端呼叫內容的存取權。

要針對伺服器覆寫的 Interceptor 方法:

  • UnaryServerHandler:攔截一元 RPC。
  • ClientStreamingServerHandler:攔截用戶端串流 RPC。
  • ServerStreamingServerHandler:攔截伺服器串流 RPC。
  • DuplexStreamingServerHandler:攔截雙向串流 RPC。

建立伺服器 gRPC 攔截器

下列程式碼提供攔截傳入一元 RPC 的範例:

public class ServerLoggerInterceptor : Interceptor
{
    private readonly ILogger _logger;

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

    public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
        TRequest request,
        ServerCallContext context,
        UnaryServerMethod<TRequest, TResponse> continuation)
    {
        _logger.LogInformation("Starting receiving call. Type/Method: {Type} / {Method}",
            MethodType.Unary, context.Method);
        try
        {
            return await continuation(request, context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error thrown by {context.Method}.");
            throw;
        }
    }
}

覆寫 UnaryServerHandler

  • 攔截傳入的一元呼叫。
  • 記錄呼叫的詳細資料。
  • 呼叫傳入方法的 continuation 參數。 如果這是最後一個攔截器,則會叫用鏈結中的下一個攔截器或服務處理常式。
  • 記錄任何例外狀況。 等候接續允許在執行服務方法之後新增邏輯。 透過在 try-catch 區塊中等候接續,即可記錄來自方法的錯誤。

用戶端與伺服器攔截器方法的簽章類似:

  • continuation 代表傳入 RPC 的委派,其呼叫鏈結中的下一個攔截器或服務處理常式 (如果鏈結中沒有任何攔截器)。 與用戶端攔截器類似,您可以隨時進行呼叫,而無需直接從接續委派傳回回應。 透過等候接續,即可在執行服務處理常式之後新增輸出邏輯。
  • context 包含與伺服器端呼叫相關聯的中繼資料,例如要求中繼資料、期限和取消或 RPC 結果。

如需如何建立伺服器攔截器的詳細資訊,請參閱 grpc/grpc-dotnet GitHub 存放庫中的 ServerLoggerInterceptor.cs 範例

設定伺服器攔截器

gRPC 伺服器攔截器是在啟動時設定。 下列程式碼範例:

  • 使用 AddGrpc 將 gRPC 新增至應用程式。
  • 透過將 ServerLoggerInterceptor 新增至服務選項的 Interceptors 集合,為所有服務設定該項目。
public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc(options =>
    {
        options.Interceptors.Add<ServerLoggerInterceptor>();
    });
}

您也可以使用 AddServiceOptions 並指定服務類型,為特定服務設定攔截器。

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddGrpc()
        .AddServiceOptions<GreeterService>(options =>
        {
            options.Interceptors.Add<ServerLoggerInterceptor>();
        });
}

攔截器會依其新增至 InterceptorCollection 的順序執行。 如果已設定全域和單一服務攔截器,則會先執行全域設定的攔截器,再執行針對單一服務設定的攔截器。

根據預設,gRPC 伺服器攔截器具有每個要求的存留期。 透過使用相依性插入註冊攔截器類型,就可以覆寫此行為。 下列範例會註冊 ServerLoggerInterceptor 的單一資料庫存留期:

public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc(options =>
    {
        options.Interceptors.Add<ServerLoggerInterceptor>();
    });

    services.AddSingleton<ServerLoggerInterceptor>();
}

gRPC 攔截器與中介軟體

相較於 C-core 型 gRPC 應用程式中的攔截器,ASP.NET Core 中介軟體提供類似的功能。 ASP.NET Core 中介軟體和攔截器在概念上相似。 兩個都:

  • 用來建構處理 gRPC 要求的管線。
  • 允許在管線的下一個元件之前或之後執行工作。
  • 提供 HttpContext 的存取權:
    • 在中介軟體中,HttpContext 是參數。
    • 在攔截器中,可以使用 ServerCallContext 參數搭配 ServerCallContext.GetHttpContext 擴充方法來存取 HttpContext。 這項功能專屬於在 ASP.NET Core 中執行的攔截器。

gRPC 攔截器與 ASP.NET Core 中介軟體的差異:

  • 攔截器:
    • 使用 ServerCallContext 在 gRPC 抽象層上操作。
    • 提供下列項目的存取權:
      • 傳送至呼叫的還原序列化訊息。
      • 序列化之前從呼叫傳回的訊息。
    • 可以擷取並處理從 gRPC 服務擲回的例外狀況。
  • 中介軟體:
    • 針對所有 HTTP 要求執行。
    • 在 gRPC 攔截器之前執行。
    • 對基礎 HTTP/2 訊息進行操作。
    • 只能存取要求和回應串流中的位元組。

其他資源