gRPC verwendet Protocol Buffers als IDL (Interface Definition Language). Der Style-Guide empfiehlt folgende Struktur:
Lizenzheader
wenn erforderlich
Dateiübersicht
Beschreibung in Textform
Syntax
Version von Protocol Buffers (aktuell 3)
Paket
Paketname in Kleinbuchstaben
Importe
Sortierte Einbindung von Datenstrukturen
Dateioptionen
Z. T. plattformspezifische Optionen wie C# Namespace
Alles andere
Z. B. Dienstdefinitionen und Datenstrukturen
Imports
Mit den Imports können, vergleichbar mit dem using in C#, andere Strukturen eingebunden werden. Diese können global, z. B. von Google stammen oder selbst entwickelt sein.
Die Servicedefinition beinhaltet die einzelnen Methoden mit ihren Aufruf- und Rückgabeparametern. Beide Parameter sind zwingend erforderlich. Die Services sollten das Suffix Service enthalten.
Die Nachrichten sind die Datenstrukturen für die Parameter und mit Klassen in C# vergleichbar. Es werden skalare und komplexe Datenstrukturen sowie Enums verwendet.
Indexierung
Serialisierte Nachrichten werden ohne Strukturinformationen übertragen. Ein eindeutiger, aber nicht zwingend fortlaufender, Index ist unverzichtbar.
Protocol Buffers liefert zahlreiche skalare Datentypen wie double, int32 oder string mit. Bei der Codeerstellung werden die Typen aus der proto-Datei in Zieltypen der Programmiersprache übersetzt. Datumswerte müssen aus dem Paket google/protobuf/timestamp.proto importiert werden.
Die Werte einer Enumeration werden ebenfalls indexiert. Der Wert 0 steht dabei für den Standardwert. Die einzelnen Werte werden großgeschrieben benannt.
C# war seit Beginn als Zielsprache für Protobuf und gRPC verfügbar. Die erste Implementierung Grpc.Core nutzte im Kern eine C-Bibliothek. Diese ist aktuell im Status Maintenance Only und gilt ab Mai 2022 als veraltet. Die neue Bibliothek grpc-dotnet ist komplett in C# geschrieben. gRPC ist vollständig in die Tool-Chain mit Visual Studio und MSBuild integriert.
Visual Studio Integration
Um Abweichungen zwischen Client und Server zu vermeiden, sollte die Proto-Datei nur einmal vorhanden sein und relativ referenziert werden. Das Nuget-Paket Grpc.Tools steuert die Codegenerierung bei jedem Build.
Visual Studio, Rider und die .NET CLI bringen Vorlagen für gRPC-Services mit.
gRPC-Services sind Teil der Pipeline von ASP.NET und können durch Kestrel bereitgestellt werden.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();
var app = builder.Build();
app.MapGrpcService<GRPC-SERVICE-CLASS>();
app.Run();
Errorhandling
Die 17 Statuswerte von gRPC sind sprachübergreifend gültig und bilden wichtige Zustände wie OK, PermissionDenied oder Unauthenticated ab. Die Anreicherung um textuelle Zusatzinformationen ist möglich.
public override Task<GetDemoResponse>GetDemo(GetDemoRequest request, ServerCallContext context)
{
...
context.Status = new Status(StatusCode.InvalidArgument, "something was wrong");
...
}
Verwendung Client
Workaround .NET Core 3.1
In HTTP/2 bzw. gRPC ist TLS zur Absicherung der Verbindung empfohlen, aber nicht zwingend erforderlich. Beim Einsatz von Reverse Proxies mit TLS-Termination wie Nginx und Traefik kann es zu Problemen kommen. Bei einem Client, der mit .NET Core 3.1 implementiert ist, muss als Workaround beim Anwendungsstart eine Option aktiviert werden.
Mit dem Nuget-Paket prometheus-net.AspNetCore.Grpc werden Metriken der gRPC-Services wie die Aufrufanzahl bereitgestellt. Prometheus benötigt zwingend eine HTTP 1.1 Verbindung. Dafür muss ggf. ein zusätzlicher Endpunkt im Kestrel aktiviert werden.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseGrpcMetrics();
app.UseEndpoints(endpoints =>
{ ...
endpoints.MapMetrics();
...
}
app.Run();
gRPC
Client-UI
BloomRPC (Archived)
GUI Client für gRPC Services
Entwickelt mit HTML, CSS und React
LGPL-3.0 License
Cross-Platfrom durch Electron
Import von Protobuf-Definitionen
Unary Calls und Streaming
Native gRPC und gRPC Web
TLS und non-TLS-Verbindungen
Postman
Unterstützung seit 2022
Unary Calls und Streaming (Client, Server, Bidirectional)
Import Service-Definition per proto-Datei oder Server Reflection
Erzeugung Beispielanfragen als JSON
Empfang und Versand von Metadaten z.B. Header
Verschlüsselter und unverschlüsselter Datenverkehr
Variableninterpolation mithilfe von Umgebungen
Importieren, bearbeiten und prüfen von Protobuf-Definitionen mit Syntax Highlighting und Autovervollständigung
Authentifizierung mit Basic Auth, Bearer Token und API-Key
Entwicklung mit C#
Streaming
Ãœberblick
Das Streaming in gRPC nutzt das Multiplexing von HTTP/2, was ermöglicht, dass über eine Verbindung mehrere Anfragen und/oder Antworten gesendet werden können. Dies ist besonders nützlich für Anwendungsfälle wie den Upload oder Download größerer Dateien sowie kontinuierliche Übertragungen wie z. B. Logs. Um einen stabilen Betrieb zu gewährleisten, müssen im Client Verbindungs- und Übertragungsfehler, z. B. durch Retry, Reconnect und Healthchecks, erkannt und behandelt werden.
Arten
Client-Streaming
Server-Streaming
Bidirectional-Streaming
Contract
Das Schlüsselwort stream in der Service-Definition kennzeichnet, dass die Nachricht möglicherweise mehrmals übertragen wird.
Client-Streaming
service ClientStreamServices {
rpc SendPartDemo (stream SendPartDemoRequest) returns (SendPartDemoResponse);
}
Server-Streaming
service ServerStreamServices {
rpc ReceivePartDemo (ReceivePartDemoRequest) returns (stream ReceivePartDemoResponse);
}
using (var streamingCall = client.ReceivePartDemo())
{
...
await foreach (var response in streamingCall.ResponseStream.ReadAllAsync())
{
...
}
}
Bidirectional-Streaming
public override async Task MultipartDemo(
IAsyncStreamReader<MultipartC2SMessage> requestStream,
IServerStreamWriter<MultipartS2CMessage> responseStream, ServerCallContext context)
{
var taskRead = readAsync(requestStream, context);
var taskWrite = WriteAsync(responseStream, context);
await Task.WhenAll(taskRead,taskWrite);
}
using (var streamingCall = client.MultipartDemo())
{
var sendTask = SendMessage(streamingCall);
var receiveTask = ReceiveMessage(streamingCall);
await Task.WhenAll(sendTask, receiveTask);
}
Entwicklung mit C#
Authentifizierung / Autorisierung
Ãœberblick
Im gRPC-Framework sind keine Konzepte zur Authentifizierung bzw. Autorisierung vorgeschrieben. Erweiterungen wie Client-Zertifikate und Token (JWT / OAuth 2) rüsten die Funktionalität nach, müssen aber sowohl im Client als auch im Server implementiert werden. Die HTTP-Pipeline von ASP.NET bzw. Kestrel bietet bereits zahlreiche Optionen.
Client-Zertifikaten
Client
X509Certificate2 certificate = LoadClientCertificate(...)
var httpClienthandler = new HttpClientHandler();
httpClienthandler.ClientCertificates.Add(certificate);
var httpClient = new HttpClient(handler);
var options = new GrpcChannelOptions { HttpClient = httpClient };
var channel = GrpcChannel.ForAddress(serviceUrl, opt);
var client = new AuthDemoServices.AuthDemoServicesClient(channel);
Service
[Authorize(AuthenticationSchemes = CertificateAuthenticationDefaults.AuthenticationScheme)]
public class AuthDemoServices : AuthDemoServices.AuthDemoServicesBase
{
...
}
Die zwei Proto-Dateien http.proto und annotations.proto müssen aktuell manuell ins Projekt kopiert werden. Sie liefern die Option google.api.http zur Beschreibung des Endpunkts.
var channel = GrpcChannel.ForAddress("dns:///grpc.service.fqdn",
new GrpcChannelOptions
{
Credentials = ChannelCredentials.Insecure,
ServiceConfig = new ServiceConfig { LoadBalancingConfigs = { new RoundRobinConfig() } }
});
var client = new MyDemo.DemoClient(channel);
Transiente Fehlerbehandlung
Transiente Fehler treten unregelmäßig auf und haben i.d.R. keinen fachlichen Hintergrund.
Kurzfristiger Verlust der Netzwerkverbindung
Zeitweise Nichtverfügbarkeit
Timeout durch Serverlast
Standard
Standardmäßig führen transiente Fehler zum Abbruch (Exception). Der Client muss den Prozess komplett neu ausführen.
Mit Wiederholung(en)
Transiente Fehler lassen sich kaum vermeiden aber meist durch einen erneuten Versuch lösen.
var mCfg = new MethodConfig{
Names = { MethodName.Default },
RetryPolicy = new RetryPolicy {
MaxAttempts = 5,
InitialBackoff = TimeSpan.FromSeconds(1),
MaxBackoff = TimeSpan.FromSeconds(5),
BackoffMultiplier = 1.5,
RetryableStatusCodes = { StatusCode.Unavailable }
}
};
var channel = GrpcChannel.ForAddress(
"https://grpc.service.fqdn:5001",
new GrpcChannelOptions { ServiceConfig =
new ServiceConfig { MethodConfigs = { mCfg } } });