gRPC ist ein von Google entwickeltes Framework mit dem Ziel, die Kommunikation und den Austausch von Daten zwischen Anwendungen effizienter zu gestalten. Es basiert auf dem Konzept Remote Procedure Call (RPC), bei dem verteilte Systeme untereinander Prozeduren (Methoden) über Adressräume hinweg ausführen können. Grundlage ist eine gemeinsame Schnittstellenspezifikation, in der alle Methoden zusammen mit Parametern und Rückgabewerten sowie der Struktur zu übertragender Daten beschrieben sind. Beteiligte Anwendungen implementieren diese Spezifikation und können anschließend untereinander Methoden anderer Anwendungen aufrufen, als handele es sich dabei um lokale Objekte.
Die Definition der Schnittstelle erfolgt mithilfe der ebenfalls von Google entwickelten Protocol Buffers (kurz Protobuf). Es handelt sich dabei um eine Beschreibungssprache zur Definition von Netzwerkdiensten sowie der darin genutzten Nachrichtenformate. Ist die Struktur der Daten erst mal definiert, kann mithilfe eines speziellen Compilers Code für diverse Programmiersprachen generiert werden. Dabei erfolgt die Beschreibung eines Dienstes mit Protocol Buffers selbst in einer eigens definierten Syntax völlig plattform-, programmiersprachen- und protokollunabhängig. Die wesentlichen Elemente einer solchen Protobuf-Datei sind die Elemente Service und Message. Ein Service umfasst Methoden einer Schnittstelle, die später von beteiligten Clients und Servern implementiert werden können. Messages stellen das Dateiformat dar, das zwischen den Systemen ausgetauscht wird.
Mittlerweile steht das gRPC-Framework für etliche Programmiersprachen (wie C#, C++, Go, Java, Node, Python usw.) zur Verfügung. Ein Vorteil dabei ist, dass Clients und Server nicht in der gleichen Sprache entwickelt sein müssen, um miteinander kommunizieren zu können. Es ist somit möglich, einen mit Java entwickelten Server mit Clients in C# und Python zusammenarbeiten zu lassen. Unter Verwendung der npm-Bibliothek grpcweb lassen sich Clients in Web-Frontends einbetten. Aufgrund von Limitation moderner Webbrowser in Bezug auf das HTTP/2-Protokoll steht jedoch nicht der volle Funktionsumfang von gRPC zur Verfügung.
Wie diese Einschränkungen aussehen und welche Fallstricke außerdem bei der Entwicklung von gRPC-basierten Webanwendungen auftreten können, zeigt dieser Artikel. In den folgenden Kapiteln wird eine Beispielanwendung zur Verwaltung einer Brettspielsammlung vorgestellt. Dabei wird ein minimales Backend erstellt, das anschließend via grpc-web an ein Frontend angebunden wird. Zugleich werden der grundlegende Aufbau sowie dessen Vor- und Nachteile diskutiert.
gRPC am Beispiel erklärt
gRPC ermöglicht bidirektionales Streaming von Inhalten und enthält dank der verwendeten Protocol Buffers nur wenig Overhead im Nachrichtenformat. Bei Verwendung der Technologie im Web-Frontend kann gRPC jedoch aufgrund von Limitationen aktueller Webbrowser nicht sein volles Potenzial entfalten. Das Problem besteht darin, dass Browser derzeit (und wahrscheinlich auch in Zukunft) aus Sicherheitsgründen keine API zur Verfügung stellen, die eine ausreichend umfangreiche Kontrolle von HTTP/2-Frames durch JavaScript erlaubt. Verbindungen müssen so anstelle des modernen HTTP/2-Protokolls weiterhin via HTTP/1.1 aufgebaut werden (vgl. [prtcl]). Um dennoch die Verwendung von gRPC im Web-Frontend zu ermöglichen, bietet Google die npm-Bibliothek grpc-web. Die Bibliothek setzt, verglichen mit nativem gRPC, auf ein angepasstes Protokoll mit dem Ziel, Einschränkungen der Browser zu überwinden. Weil jedoch das HTTP-Protokoll selbst Teil des Problems ist, lässt sich die Herausforderung allein mit einer Web-Bibliothek nicht bewältigen. Aus diesem Grund wird eine zusätzliche Infrastruktur-Komponente benötigt, die den Datenverkehr konvertiert.
Die offizielle Dokumentation empfiehlt dazu die Verwendung eines Envoy-Proxys, da dieser gRPC bereits nativ beherrscht. Der Envoy-Proxy nimmt Anfragen des Web-Frontends auf Grundlage des HTTP/1.1-Protokolls an, konvertiert diese ins gRPC-HTTP/2-Format und reicht sie an das Backend weiter. Antworten des Servers werden entsprechend auf umgekehrte Weise konvertiert. Weitere Proxy-, Gateway- oder Service-Varianten sind ebenfalls möglich und bieten teilweise spezialisierte gRPC-Konfigurationen oder -Plug-ins an (vgl. [proxy]).
Für den Betrieb einer Anwendung mit gRPC im Web werden somit in Summe mindestens drei Bestandteile benötigt:
- ein gRPC-Backend,
- ein JavaScript basiertes Frontend, das die grpc-web-Bibliothek einbindet,
- ein Proxy zur Konvertierung der Anfragen zwischen Frontend und Backend.
Nachfolgend wird die Einrichtung der drei Komponenten beschrieben. Als Beispiel dient hier ein Service zur Verwaltung einer Brettspielsammlung wie er auch bereits in [BiKa23] beschrieben wurde. Anhand des Beispiels werden die Erstellung und Erweiterung verschiedener Funktionen erläutert, sodass im Verlauf des Artikels ein technischer Durchstich vom Web-Frontend in ein gRPC-Backend entsteht. Das Beispiel ist hierzu für eine bessere Lesbarkeit bewusst einfach gehalten. Weiterführende Themen wie komplexe Datentypen, Verschachtelung von Objekten, Fehlerbehandlung, Statuscodes, Datenvalidierung usw. können dem genannten Artikel entnommen werden.
Listing 1 zeigt die Protobuf-Datei für diesen BoardGameCollectionService.
Listing 1: Beispiel der Protbuf-Daten- und -Service-Beschreibung (bg_collection.proto)
Es wird der Service BGCollection erstellt, der eine Methode zum Hinzufügen eines BoardGames und eine weitere zum Abrufen aller vorhandenen BoardGames enthält. Jeder Methode ist im Protocol Buffer das Wort rpc vorangestellt, anschließend folgt der Methodenname sowie Übergabeparameter und Rückgabewert. In Protobuf basiert die gesamte Kommunikation auf Messages, weshalb die Spezifikation immer exakt einen Übergabeparameter und einen Rückgabewert erfordert.
BoardGames selbst sind in Form einer Message, der Struktureinheit von Protobuf um Daten zu beschreiben, definiert. Jedes Attribut der Message besteht aus dem Datentyp, einem Attributnamen und einer Nummer. Die Nummer muss innerhalb einer Message einzigartig sein und dient zur Identifikation der Felder im Binärformat. Bestehende Nummern sollten nachträglich nicht mehr verändert werden (vgl. [proto]).
Erstellen eines Backends
Dieser Artikel verwendet Python zur Erstellung eines Beispiel-Backends. Grundsätzlich ist die verwendete Sprache des Backends aber unerheblich für die spätere Anbindung der Webanwendung.
Im ersten Schritt müssen die benötigten Bibliotheken installiert werden. Konkret sind dies grpcio zur Integration der Kernfunktionalität sowie grpc-tools für die Verwendung des Protocol Buffers Compilers.
Die Bibliotheken werden mithilfe der folgenden Befehle installiert:
$ python -m pip install grpcio
$ python -m pip install grpciotool
Die .proto-Datei wird auf Basis des vorherigen Abschnittes erstellt. Mithilfe des protoc-Compilers werden aus dieser die Interfaces für das Backend erstellt.
Via Kommandozeile wird der soeben installierte Compiler aufgerufen:
$ python -m grpc_tools.protoc
--proto_path=. ./bg_collection.
proto --python_out=.
--grpc_python_out=.
Der Compiler generiert daraufhin die Dateien bg_collection_pb2.py und bg_ collection_pb2_grpc.py. Diese enthalten Code zum Instanziieren von BoardGame-Messages sowie Methoden, um den BG-Collection-Service einem gRPC-Server hinzuzufügen.
Listing 2 zeigt den möglichen Aufbau einer einfachen Server-Anwendung, die die beiden Methoden der Protobuf-Datei implementiert.
Listing 2: Server-Beispiel (bg_collection_server.py)
Zu Beginn der Klasse BG-CollectionService wird eine Instanz von BoardGames erzeugt, um hinzugefügte BoardGames zu speichern. In realen Szenarien würde hier keine einfache Variable, sondern die Logik zur Persistierung von BoardGames ihren Platz finden.
Es folgt die Implementierung der beiden Methoden auf Basis der im Protocol Buffer beschriebenen Struktur. Die Umsetzung ist bewusst einfach gehalten, um die Anschaulichkeit des Beispiels zu erhöhen. An dieser Stelle würde sich in realen Anwendungen das Mapping von Messages auf andere Datenstrukturen anbieten.
Nachdem die Business-Logik vollständig implementiert ist, folgt unter Verwendung der generierten Dateien ein grundlegendes Setup des gRPC-Servers. Dazu wird der aus der .proto-Datei generierte und importierte Service dem gRPC-Server hinzugefügt und der Server wird gestartet.
gRPC-web ins Frontend integrieren
In diesem Abschnitt wird die Verwendung der Bibliothek grpc-web in einer Node. js-Umgebung mit JavaScript beschrieben. Alternativ wird auch TypeScript mittlerweile von gRPC unterstützt, ist aber noch als experimentell gekennzeichnet. Die gezeigten Beispiele sind Framework-unabhängig und können in der Regel ohne Anpassungen in Web-Frameworks (wie Angular, React, Vue.js usw.) integriert werden.
Bei der Suche nach gRPC-Bibliotheken auf npmjs.com fällt auf, dass es etliche Bibliotheken mit nahezu identischen Namen gibt, die auf den ersten Blick ähnlich erscheinen. Daher ein kurzer Überblick über die erhältlichen Bibliotheken:
- grpc: Googles erste Version einer gRPC-Bibliothek für Node.js-Umgebungen. Diese Version ist veraltet (deprecated) und sollte nicht mehr verwendet werden.
- grpc-js: Nachfolgeversion der grpc-Bibliothek, ebenfalls von Google entwickelt. Die Dependency ermöglicht eine Nutzung des gRPC-Frameworks in Node.js-Umgebungen. Es können sowohl Clients als auch Server erstellt werden. Die Bibliothek verwendet das HTTP/2.0-Protokoll und kann daher nicht für Webanwendungen im Browser verwendet werden.
- grpc-web: Googles JavaScript-Bibliothek, um den Zugriff von Web-Clients im Browser auf gRPC-Services zu ermöglichen. Google beschreibt den derzeitigen Stand der Umsetzung als "generally available, and considered stable enough for production use" (vgl. [npmjs]).
Um nachfolgend die Einrichtung eines Web-Frontends zu beschreiben, wird die Bibliothek grpc-web verwendet, die zunächst installiert werden muss:
$ npm install --save grpc-web
Wie auch im Backend muss die Protobuf-Datei mithilfe eines protoc-Compilers für die jeweilige Zielsprache kompiliert werden. In Fall von JavaScript wird dazu zusätzlich das protoc-Plug-in protoc-gengrpc-web benötigt. Dieses kann aus den Github Releases von grpc-web heruntergeladen werden und muss (so wie zuvor der protoc-Compiler) ausführbar und via PATH auffindbar sein. (vgl. [gitrel])
Bei Ausführung des nachfolgenden Befehls wird der protoc-Compiler zusammen mit dem protoc-gen-grpc-web-Plug-in aufgerufen, um die Messages und den Service-Client-Stub aus der Protobuf-Datei zu erzeugen:
$ protoc bg_collection.proto
--js_out=import_
style=commonjs:generated
--grpc-web_out=import_style=com
monjs, mode=grpcwebtext:generated
Als Ergebnis entstehen zwei Dateien:
- Die Datei bg_collection_pb.js enthält Code zum Instanziieren der zuvor im Protocol Buffer definierten Messages, darunter die BoardGame-Message.
- Die Datei bg_collection_grpc_web_ pb.js beinhaltet Funktionen zur Erstellung eines BGCollectionClient, der für die Kommunikation mit einem backendseitigen BGCollection-Service verwendet werden kann.
Die beiden Dateien müssen in einem Verzeichnis innerhalb des node-Projekts abgelegt werden. In diesem Beispiel wird dieses Verzeichnis "generated" genannt. Der erste Schritt der Frontend-Implementierung besteht darin, die erzeugten Dateien zu importieren. In JavaScript kann dazu die Standardfunktion require() verwendet werden.
In Listing 3 ist ein Verbindungsaufbau zum gRPC-Server samt Aufruf der Methoden für das Hinzufügen eines Board-Games und zum Abrufen vorhandener BoardGames gezeigt.
Listing 3: Client-Beispiel (BgWebClient.js)
Zu Beginn wird eine Instanz des importierten BGCollection-Client erzeugt. Als Parameter muss die Adresse des Proxys übergeben werden, der die Nachrichten des Browsers in das gRPC-HTTP/2.0-Format übersetzt. Die Einrichtung und Konfiguration dieses Proxys ist im nächsten Kapitel beschrieben.
In diesem Beispiel wird von einem lokal (auf localhost) laufenden Proxy ausgegangen. Mithilfe des Clients können gemäß des Remote Procedure-Ansatzes die serverseitigen Methoden aufgerufen werden. Zunächst wird eine Instanz einer BoardGame-Message erzeugt, die später an das Backend übergeben werden soll. Durch Konstruktoraufruf wird ein BoardGame-Objekt erstellt und mithilfe von Set-Methoden werden die Attribute entsprechend ihrem Datentyp befüllt. Ist die Message vollständig erstellt, folgt der Aufruf von addBoardGame am Client. Alle Methoden am Client haben immer die gleichen drei Parameter, die angegeben werden müssen:
- Ein Payload, der an den gRPC-Service übergeben wird. Im Fall von addBoardGame ist dies die erstellte BoardGame-Message. Die Methode getBoardGames benötigt sinngemäß eigentlich keinen Payload, jedoch sind Übergabeparameter verpflichtend durch die Protocol-Buffers-Syntax vorgeschrieben, weshalb hier eine leere Message übergeben wird.
- Ein Header-Objekt, das zum Beispiel Konfigurationen zwecks Authentifizierung oder weitere Custom-Header enthalten kann.
- Eine Callback-Function mit zwei Parametern, die im Fehlerfall weitere Details enthalten beziehungsweise bei Erfolg die Antwort-Message bereitstellen.
Die Ergebnisliste von getBoardGames kann durch Aufruf des generierten Getters getCollectionList erreicht werden.
Der Envoy-Proxy zur Konvertierung der Anfragen zwischen Front- und Backend
Für die Kommunikation zwischen Frontend und Backend wird aufgrund von Einschränkungen moderner Browser in Hinsicht auf das implementierte HTTP-Protokoll ein Gateway-Proxy zur Übersetzung von Anfragen und Antworten benötigt. Envoy ist der offiziell empfohlene Proxy und bringt bereits nativ Support für grpc-web mit. Neue Funktionen sollen laut gRPC-Team stets vor anderen Proxys im Envoy implementiert werden (vgl. [browser]). Grundsätzlich gibt es jedoch auch andere Proxys, die zusammen mit grpc-web verwendet werden können. Envoy kann kostenlos von der Herstellerseite www.envoyproxy.io für diverse Zielsysteme heruntergeladen werden. Nach der Installation muss eine Konfiguration für den gRPC-Anwendungsfall angelegt werden. Solche Konfigurationsdateien sind in verschiedenen Quellen im Internet zu finden. Für einen schnellen Start lassen sich diese in der Regel ohne tiefe Einarbeitung oder Anpassungen nutzen. Innerhalb der Konfigurationsdatei lassen sich der Port, auf den der Proxy für gRPC-Anfragen hört, sowie IP-Adresse und Port des gRPC-Backends anpassen. Zu beachten ist, dass der Client im Frontend die Adresse des Proxys für Anfragen verwenden muss und nicht Adresse und Port des Backends (siehe die Instanziierung des BGCollectionClient in Listing 3).
Ist der Proxy installiert und die Konfiguration angelegt, kann dieser gestartet werden:
$ envoy -c proxy_config.yaml
Wenn das Backend ausgeführt und der Proxy gestartet ist, lässt sich mithilfe von curl testen, ob der Proxy in der Lage ist, das Backend zu erreichen:
$ curl -v localhost:8080
In diesem Beispiel hört der Proxy auf den Port "8080" und es werden alle drei Komponenten (Frontend, Backend und Proxy) lokal ausgeführt.
Konnte curl den Proxy erreichen und dieser wiederum das Backend, gibt es die Antwort "HTTP 200 OK" zusammen mit "content-type: application/grpc". War zwar der Proxy erreichbar, aber dieser konnte das Backend nicht finden (oder das Backend ist nicht gestartet), antwortet Envoy mit „HTTP 503 Service Unavailable".
Funktionsumfang von gRPC-web
Bisher wurde anhand des Beispiels gezeigt, wie einfache RPC-Methoden (sog. "simple RPC" oder "unary RPC") aufgerufen werden können. Ähnlich wie bei einem Methodenaufruf sendet der Client eine Anfrage an den Server und wartet so lange, bis eine Antwort erhalten wird.
Natives gRPC bietet darüber hinaus die Möglichkeit, Daten als kontinuierlichen Stream bereitzustellen, wobei zwischen Server-, Client- und bidirektionalem Streaming unterschieden wird. Hierbei sendet der Server beziehungsweise Client einen Stream von Messages, die fortlaufend von der jeweiligen Gegenstelle empfangen und bearbeitet werden. Im Fall von bidirektionalem Streaming können sogar beide Seiten zugleich und unabhängig voneinander Daten übertragen und verarbeiten (vgl. [core]).
grpc-web unterstützt derzeit nur unary RPCs und Server-Streaming – und zweiteres auch nur, wenn zuvor bei Aufruf des protoc-Compilers der grpcwebtext-Modus gewählt wurde (vgl. [gitweb]).
Die Funktionen Client-Streaming und bidirektionales Streaming sollen laut offizieller Roadmap realisiert werden, sobald Full-duplex Streaming via WebTransport von gängigen Browsern unterstützt wird (vgl. [gitmap]). Um dazu etwas Kontext zu liefern: Die WebTransport-API ist eine auf HTTP/3 basierende Technologie, die es Clients ermöglichen soll, selbstständig Verbindungen zu HTTP/3-Servern aufzubauen und Daten in beide Richtungen zu übertragen. Die Spezifikation der Web-Transport-API befindet sich derzeit noch im Entwurfszustand (vgl. [webtrns]). Bis grpc-web einen identischen Funktionsumfang wie natives gRPC bietet, dürfte demnach wahrscheinlich noch etwas Zeit vergehen.
Für den Moment bedeutet dies, ein bestehender gRPC-Service muss für jede Client-Streaming-Methode eine Nicht-Streaming-Variante als Alternative anbieten, sobald dieser in Verbindung mit grpc web genutzt werden soll.
Fazit
Die Bibliothek grpc-web ermöglicht die Integration der gRPC-Technologie in Web-Frontends. Dabei kann von etlichen der gRPC-eigenen Vorteile profitiert werden. Protocol Buffers dient auch hier als neutrale Schnittstellenbeschreibungssprache und erlaubt die Service-übergreifende Definition von Datenmodellen und Methoden.
Aufgrund des in Webbrowsern vorherrschenden Protokolls büßt gRPC im Web jedoch einige seiner Mehrwerte ein: Für die Kommunikation zwischen Frontend und Backend muss ein Übersetzungsproxy eingesetzt werden und es stehen nicht alle Streaming-Methoden zur Verfügung. Für Teams, die ein bestehendes gRPC-Backend um ein Web-Frontend erweitern möchten, ist grpc-web dennoch eine interessante Option, die die umfangreiche Neuentwicklung von Backendstrukturen vermeiden kann.
Letztlich hängt die Frage, für wen sich gRPC im Web lohnt, von den spezifischen Anforderungen und dem Kontext der eigenen Anwendung ab. Wird gRPC bereits in der eigenen Serverarchitektur verwendet und ist eine Webanwendung geplant, die mit diesem Server kommunizieren soll, ist grpc-web eine offensichtliche Wahl.
Weitere Informationen
[BiKa23] S. Bindick, T. Karger, gRPC und Protocol Buffers. Ein moderner Ansatz zur Integration von Microservices, IT Spektrum, 03/2023
[browser] gRPC-Web features for browser (HTML) clients, 2023, siehe:
https://github.com/grpc/grpc-web/blob/master/doc/browser-features.md
[core] RPC life cycle – gRPC Core concepts, architecture and lifecycle, siehe:
https://grpc.io/docs/what-is-grpc/core-concepts/
[gitmap] grpc-web Roadmap, siehe:
https://github.com/grpc/grpc-web/blob/master/doc/streaming-roadmap.md#chrome-origin-trial-on-upload-streaming
[gitrel] grpc-web Releases, 2023, siehe:
https://github.com/grpc/grpc-web/releases
[gitweb] grpc-web Github Readme, siehe:
https://github.com/grpc/grpc-web
[npmjs] gRPC-Web Client Runtime Library, 2023, siehe:
https://www.npmjs.com/package/grpc-web
[proto] Protocol Buffers Documentation, siehe:
https://protobuf.dev
[proxy] gRPC-Web Proxy Interoperability, 2023, siehe:
https://github.com/grpc/grpc-web#proxy-interoperability
[prtcl] gRPC Web-Protocol Documentation, 2023, siehe:
https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md
[webtrns] w3c: WebTransport – Editor’s Draft, 18.07.23, siehe;
https://w3c.github.io/webtransport/