gRPC-Interceptors in .NET
Hinweis
Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.
Warnung
Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der Supportrichtlinie für .NET und .NET Core. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.
Wichtig
Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.
Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.
Von Ernest Nguyen
Interceptors sind ein gRPC-Konzept, das es Apps ermöglicht, mit ein- oder ausgehenden gRPC-Aufrufen zu interagieren. Sie bieten eine Möglichkeit zum Anreichern der Anforderungspipeline.
Interceptors werden für einen Kanal oder Dienst konfiguriert und bei jedem gRPC-Aufruf automatisch ausgeführt. Da Interceptors für die Anwendungslogik des Benutzers transparent sind, eignen sie sich hervorragend für gängige Zwecke wie Protokollierung, Überwachung, Authentifizierung und Validierung.
Interceptor
-Typ
Interceptors können für gRPC-Server und -Clients implementiert werden, indem Sie eine Klasse erstellen, die vom Typ Interceptor
erbt:
public class ExampleInterceptor : Interceptor
{
}
Standardmäßig hat die Basisklasse Interceptor
keine Aufgabe. Fügen Sie einem Interceptor Verhalten hinzu, indem Sie die entsprechenden Methoden der Basisklasse in einer Implementierung des Interceptors überschreiben.
Clientinterceptors
gRPC-Clientinterceptors fangen ausgehende RPC-Aufrufe ab. Sie ermöglichen den Zugriff auf die gesendete Anforderung, die eingehende Antwort und den Kontext für einen Aufruf auf Clientseite.
Interceptor
-Methoden, die für den Client überschrieben werden sollen:
BlockingUnaryCall
: fängt einen blockierenden Aufruf eines unären RPC ab.AsyncUnaryCall
: fängt einen asynchronen Aufruf eines unären RPC ab.AsyncClientStreamingCall
: fängt einen asynchronen Aufruf eines RPC für Clientstreaming ab.AsyncServerStreamingCall
: fängt einen asynchronen Aufruf eines RPC für Serverstreaming ab.AsyncDuplexStreamingCall
: fängt einen asynchronen Aufruf eines RPC für bidirektionales Streaming ab.
Warnung
Obwohl sowohl BlockingUnaryCall
als auch AsyncUnaryCall
auf unäre RPCs verweisen, sind sie nicht austauschbar. Ein blockierender Aufruf wird nicht von AsyncUnaryCall
abgefangen, ein asynchroner Aufruf nicht von BlockingUnaryCall
abgefangen.
Erstellen eines gRPC-Interceptors für einen Client
Der folgende Code ist ein einfaches Beispiel für das Abfangen eines asynchronen Aufrufs eines unären Aufrufs:
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);
}
}
Das Überschreiben von AsyncUnaryCall
bewirkt Folgendes:
- Ein asynchroner unärer Aufruf wird abgefangen.
- Details zum Aufruf werden protokolliert.
- Der an die Methode übergebene Parameter
continuation
wird aufgerufen. Dies ruft den nächsten Interceptor in der Kette oder den zugrunde liegenden Aufrufaufrufer auf, wenn dies der letzte Interceptor ist.
Methoden für Interceptor
für jede Art von Dienstmethode haben unterschiedliche Signaturen. Das Konzept hinter den Parametern continuation
und context
bleibt jedoch unverändert:
continuation
ist ein Delegat, der den nächsten Interceptor in der Kette oder den zugrundeliegenden Aufrufer aufruft (wenn es keinen Interceptor mehr in der Kette gibt). Es ist kein Fehler, ihn NULL oder mehrmals aufzurufen. Interceptors müssen keine Aufrufdarstellung (AsyncUnaryCall
im Falle einer unären RPC) zurückgeben, die vom Delegatencontinuation
zurückgegeben wird. Wenn Sie den Aufruf des Delegaten weglassen und Ihre eigene Instanz der Aufrufdarstellung zurückgeben, wird die Kette der Interceptors unterbrochen und die zugehörige Antwort sofort zurückgegeben.context
enthält bereichsbezogene Werte, die mit dem Aufruf auf Clientseite verbunden sind. Übergeben Sie mithilfe voncontext
Metadaten wie Sicherheitsprinzipale, Anmeldeinformationen oder Ablaufverfolgungsdaten. Darüber hinauscontext
enthält Informationen zu Fristen und Abbrüchen. Weitere Informationen finden Sie unter Zuverlässige gRPC-Dienste mit Fristen und Abbrüchen.
Warten auf Antwort im Clientinterceptor
Ein Interceptor kann die Antwort in unären und Clientstreamingaufrufen abwarten, indem er den Wert AsyncUnaryCall<TResponse>.ResponseAsync
oder AsyncClientStreamingCall<TRequest, TResponse>.ResponseAsync
aktualisiert.
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);
}
}
}
Der obige Code:
- Erstellt einen neuen Interceptor, der
AsyncUnaryCall
überschreibt. - Das Überschreiben von
AsyncUnaryCall
bewirkt Folgendes:- Der Parameter
continuation
wird aufgerufen, um das nächste Element in der Interceptorkette aufzurufen. - Es wird eine neue
AsyncUnaryCall<TResponse>
-Instanz basierend auf dem Ergebnis der Fortsetzung erstellt. - Die Aufgabe
ResponseAsync
wird mit der MethodeHandleResponse
umschlossen. - Die Antwort wird mit
HandleResponse
erwartet. Durch das Warten auf die Antwort kann Logik hinzugefügt werden, nachdem der Client die Antwort empfangen hat. Durch das Warten auf die Antwort in einem try-catch-Block können Fehler aus Aufrufen protokolliert werden.
- Der Parameter
Weitere Informationen zur Erstellung eines Clientinterceptors finden Sie im Beispiel zu ClientLoggerInterceptor.cs
im grpc/grpc-dotnet
GitHub-Repository.
Konfigurieren von Clientinterceptora
gRPC-Clientinterceptors werden für einen Kanal konfiguriert.
Der folgende Code führt folgende Aktionen aus:
- Ein Kanal wird unter Verwendung von
GrpcChannel.ForAddress
erstellt. - Mithilfe der Erweiterungsmethode
Intercept
wird der Kanal für die Verwendung des Interceptors konfiguriert. Beachten Sie, dass diese MethodeCallInvoker
zurückgibt. Stark typisierte gRPC-Clients können wie ein Kanal über einen Aufrufer erstellt werden. - Ein Client wird über den Aufrufer erstellt. Vom Client ausgehende gRPC-Aufrufe führen automatisch den Interceptor aus.
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var invoker = channel.Intercept(new ClientLoggerInterceptor());
var client = new Greeter.GreeterClient(invoker);
Die Erweiterungsmethode Intercept
kann verkettet werden, um für einen Kanal mehrere Interceptors zu konfigurieren. Alternativ dazu gibt es die Überladung Intercept
, die mehrere Interceptors zulässt. Für einen einzelnen gRPC-Aufruf kann eine beliebige Anzahl von Interceptors ausgeführt werden, wie das folgende Beispiel zeigt:
var invoker = channel
.Intercept(new ClientTokenInterceptor())
.Intercept(new ClientMonitoringInterceptor())
.Intercept(new ClientLoggerInterceptor());
Interceptors werden in umgekehrter Reihenfolge der verketteten Intercept
-Erweiterungsmethoden aufgerufen. Im vorstehenden Code werden Interceptors in der folgenden Reihenfolge aufgerufen:
ClientLoggerInterceptor
ClientMonitoringInterceptor
ClientTokenInterceptor
Informationen zum Konfigurieren von Interceptors mit der gRPC-Clientfactory finden Sie unter gRPC-Clientfactoryintegration in .NET.
Serverinterceptors
gRPC-Serverinterceptors fangen eingehende RPC-Anforderungen ab. Sie ermöglichen den Zugriff auf die eingehende Anforderung, die ausgehende Antwort und den Kontext für einen Aufruf auf Serverseite.
Interceptor
-Methoden, die für den Server überschrieben werden sollen:
UnaryServerHandler
: fängt einen unären RPC ab.ClientStreamingServerHandler
: fängt einen RPC für Clientstreaming ab.ServerStreamingServerHandler
: fängt einen RPC für Serverstreaming ab.DuplexStreamingServerHandler
: fängt einen RPC für bidirektionales Streaming ab.
Erstellen eines gRPC-Serverinterceptors
Der folgende Code zeigt ein Beispiel für das Abfangen eines eingehenden unären 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;
}
}
}
Das Überschreiben von UnaryServerHandler
bewirkt Folgendes:
- Ein eingehender unärer Aufruf wird abgefangen.
- Details zum Aufruf werden protokolliert.
- Der an die Methode übergebene Parameter
continuation
wird aufgerufen. Dies ruft den nächsten Interceptor in der Kette oder den Diensthandler auf, wenn dies der letzte Interceptor ist. - Alle Ausnahmen werden protokolliert. Durch das Warten auf die Fortsetzung kann Logik hinzugefügt werden, nachdem die Dienstmethode ausgeführt wurde. Durch das Warten auf die Fortsetzung in einem try-catch-Block können Fehler aus Methoden protokolliert werden.
Die Signatur der Methoden von Client- und Serverinterceptors ist ähnlich:
continuation
steht für einen Delegaten eines eingehenden RPC, der den nächsten Interceptor in der Kette oder den Diensthandler aufruft (wenn es in der Kette keinen Interceptor mehr gibt). Ähnlich wie bei Clientinterceptors können Sie ihn jederzeit aufrufen und müssen nicht direkt eine Antwort vom Fortsetzungsdelegaten zurückgeben. Ausgehende Logik kann hinzugefügt werden, nachdem ein Diensthandler durch Warten auf die Fortsetzung ausgeführt wurde.context
enthält Metadaten, die mit dem serverseitigen Aufruf verbunden sind, z. B. Anforderungsmetadaten, Fristen und Abbrüche oder RPC-Ergebnisse.
Weitere Informationen zur Erstellung eines Serverinterceptors finden Sie im Beispiel zu ServerLoggerInterceptor.cs
im grpc/grpc-dotnet
GitHub-Repository.
Konfigurieren von Serverinterceptors
gRPC-Serverinterceptors werden beim Start konfiguriert. Der folgende Code führt folgende Aktionen aus:
- gRPC wird mit
AddGrpc
zur App hinzugefügt. ServerLoggerInterceptor
wird für alle Dienste konfiguriert, indem es der SammlungInterceptors
der Dienstoption hinzugefügt wird.
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc(options =>
{
options.Interceptors.Add<ServerLoggerInterceptor>();
});
}
Ein Interceptor kann auch für einen bestimmten Dienst konfiguriert werden, indem Sie AddServiceOptions
verwenden und den Diensttyp angeben.
public void ConfigureServices(IServiceCollection services)
{
services
.AddGrpc()
.AddServiceOptions<GreeterService>(options =>
{
options.Interceptors.Add<ServerLoggerInterceptor>();
});
}
Interceptors werden in der Reihenfolge ausgeführt, in der sie InterceptorCollection
hinzugefügt werden. Wenn sowohl globale als auch einzelne Interceptors für einen Dienst konfiguriert sind, werden die global konfigurierten Interceptors vor den für einen einzelnen Dienst konfigurierten Interceptors ausgeführt.
Standardmäßig haben gRPC-Serverinterceptors eine anforderungsbezogene Lebensdauer. Sie können dieses Verhalten überschreiben, indem Sie den Interceptortyp mit Dependency Injection registrieren. Das folgende Beispiel registriert ServerLoggerInterceptor
mit einer Singletonlebensdauer:
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc(options =>
{
options.Interceptors.Add<ServerLoggerInterceptor>();
});
services.AddSingleton<ServerLoggerInterceptor>();
}
Vergleich von gRPC-Interceptors und Middleware
ASP.NET Core-Middleware bietet ähnliche Funktionen wie Interceptors in C-core-basierten gRPC-Apps. ASP.NET Core-Middleware und Interceptors sind konzeptionell ähnlich. Beide:
- werden dafür verwendet, eine Pipeline zu konstruieren, die gRPC-Anforderungen verarbeitet.
- ermöglichen das Ausführen von Arbeiten vor oder nach der nächsten Komponente in der Pipeline.
- ermöglichen den Zugriff auf
HttpContext
:- In Middleware ist
HttpContext
ein Parameter. - Bei Interceptors kann auf
HttpContext
über den ParameterServerCallContext
mit der ErweiterungsmethodeServerCallContext.GetHttpContext
zugegriffen werden. Dieses Feature ist spezifisch für Interceptors, die in ASP.NET Core ausgeführt werden.
- In Middleware ist
Unterschiede zwischen gRPC-Interceptors und ASP.NET Core-Middleware:
- Interceptors:
- arbeiten auf der gRPC-Abstraktionsebene mit
ServerCallContext
. - stellen Zugriff bereit auf:
- die deserialisierte, beim Aufruf gesendete Nachricht.
- die beim Aufruf zurückgegebene Nachricht, bevor sie serialisiert wird.
- können von gRPC-Diensten ausgelöste Ausnahmen abfangen und verarbeiten.
- arbeiten auf der gRPC-Abstraktionsebene mit
- Middleware:
- wird für alle HTTP-Anforderungen ausgeführt.
- wird vor gRPC-Interceptors ausgeführt.
- arbeitet mit den zugrundeliegenden HTTP/2-Nachrichten.
- kann nur auf Bytes aus den Anforderungs- und Antwortdatenströmen zugreifen.