Browser- und gRPC-Kommunikation mit Gin und gRPC-Web überbrücken
Takashi Yamamoto
Infrastructure Engineer · Leapcell

Einleitung
In der modernen Webentwicklung sind Microservice-Architekturen zu einem Eckpfeiler für den Aufbau skalierbarer und wartbarer Anwendungen geworden. gRPC mit seiner hohen Leistung, starken Typisierung und dem effizienten Binärprotokoll auf Basis von Protocol Buffers ist eine ausgezeichnete Wahl für die Inter-Service-Kommunikation. Eine erhebliche Herausforderung ergibt sich jedoch, wenn versucht wird, gRPC-Dienste direkt mit Webbrowsern zu integrieren. Browser verstehen nativ HTTP/1.1 und JSON, nicht das von gRPC verwendete HTTP/2 und binäre Protobuf-Format. Dieser Impedanzunterschied erforderte historisch komplexe Vermittler oder RESTful-Proxy-Schichten. Dieser Artikel befasst sich damit, wie Gin, ein hoch performantes HTTP-Webframework für Go, mit gRPC-Web kombiniert werden kann, um diese Lücke effektiv zu schließen und Browsern die direkte Kommunikation mit gRPC-Diensten zu ermöglichen. Dieser Ansatz vereinfacht die Architektur, nutzt die Vorteile von gRPC durchgängig und befähigt Entwickler, robustere und effizientere Full-Stack-Anwendungen zu erstellen.
Kernkonzepte und Implementierung
Bevor wir uns mit der Implementierung befassen, definieren wir die beteiligten Kerntechnologien:
Die wichtigsten Technologien verstehen
- gRPC (gRPC Remote Procedure Calls): Ein hochperformantes, Open-Source universelles RPC-Framework, das Protocol Buffers als seine Interface Definition Language (IDL) und HTTP/2 für den Transport verwendet. Es unterstützt verschiedene Sprachen, Streaming und effiziente Serialisierung, was es ideal für Microservices und Polyglot-Umgebungen macht.
- Protocol Buffers (Protobuf): Ein sprachneutraler, plattformneutraler, erweiterbarer Mechanismus zur Serialisierung strukturierter Daten. Es ist kleiner, schneller und einfacher als XML oder JSON für die Datenmaskierung, insbesondere für die Inter-Service-Kommunikation.
- gRPC-Web: Eine Spezifikation und eine Reihe von Bibliotheken, die es Webanwendungen ermöglichen, direkt vom Browser aus mit gRPC-Diensten zu interagieren. Es fungiert als Brücke, die browserkompatible Anfragen (HTTP/1.1, XHR/Fetch) in gRPC-Nachrichten übersetzt und umgekehrt, was oft einen Proxy erfordert, der sowohl gRPC als auch gRPC-Web versteht.
- Gin: Ein schnelles, leichtgewichtiges und erweiterbares Webframework für Go. Es wird häufig zum Erstellen von RESTful-APIs verwendet und kann als ausgezeichneter HTTP-Server für die Verarbeitung von gRPC-Web-Anfragen dienen.
- Envoy Proxy: Ein beliebter Open-Source Edge- und Service-Proxy, der für Cloud-Native-Anwendungen entwickelt wurde. Er wird häufig als Reverse-Proxy, Load Balancer und API-Gateway verwendet. Im Kontext von gRPC-Web kann Envoy als entscheidender Vermittler fungieren, der gRPC-Web-Anfragen vom Browser in native gRPC für den Backend-Dienst übersetzt.
Das Funktionsprinzip
Die Kernidee ist, dass ein Webbrowser, der unfähig ist, natives gRPC (HTTP/2) zu sprechen, über gRPC-Web (HTTP/1.1 mit speziellen Headern) mit einem Proxy-Dienst kommuniziert. Dieser Proxy übersetzt dann diese gRPC-Web-Anfragen in Standard-gRPC-Aufrufe (HTTP/2) und leitet sie an den eigentlichen gRPC-Backend-Dienst weiter. Die Antwort folgt dem umgekehrten Weg. Gin wird unser Standard-HTTP-Server sein, der sowohl die Frontend-Statikdateien hostet als auch als gRPC-Web-Proxy fungiert, wenn wir die Proxy-Logik in die Go-Anwendung einbetten, oder, üblicherweise, mit einem externen Envoy-Proxy interagiert, der die gRPC-Web-Übersetzung übernimmt.
Wir veranschaulichen dies mit einem gängigen Setup, bei dem Gin die Webanwendung hostet und ein Envoy-Proxy die gRPC-Web-Übersetzung vornimmt.
Schritt-für-Schritt-Implementierung
Wir werden den Prozess durch den Aufbau eines einfachen "Greeter"-Dienstes skizzieren.
1. Definieren Sie den Protocol Buffer Service
Definieren Sie zuerst Ihren gRPC-Dienst mit Protocol Buffers. Erstellen Sie proto/greeter.proto
:
syntax = "proto3"; option go_package = "github.com/your/repo/greetpb"; package greet; service Greeter { rpc SayHello (HelloRequest) returns (HelloResponse) {} } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; }
Generieren Sie Go-Code für die gRPC-Dienst- und gRPC-Web-Client-Stubs:
# Installieren Sie protoc und Go gRPC-Plugins go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest go install github.com/grpc-ecosystem/grpc-web/protoc-gen-go-grpc-web@latest # Generieren Sie Go gRPC-Code protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ proto/greeter.proto # Generieren Sie gRPC-Web-Client-Stubs (z.B. für TypeScript) protoc --js_out=import_style=commonjs,binary:. --grpc-web_out=import_style=typescript,mode=grpcwebtext:. \ proto/greeter.proto
Dies generiert greetpb/greeter_grpc.pb.go
, greetpb/greeter.pb.go
für den Server und greeter_pb.js
, greeter_grpc_web_pb.d.ts
für das Frontend.
2. Implementieren Sie den gRPC-Server in Go
Erstellen Sie einen gRPC-Server mit dem generierten Go-Code. Datei: server/main.go
package main import ( "context" "fmt" "log" "net" "google.golang.org/grpc" "google.golang.org/grpc/reflection" // Für gRPC Bloom Service Discovery "github.com/your/repo/greetpb" // Ersetzen Sie dies durch Ihren tatsächlichen Pfad ) type server struct { greetpb.UnimplementedGreeterServer } func (s *server) SayHello(ctx context.Context, in *greetpb.HelloRequest) (*greetpb.HelloResponse, error) { log.Printf("Received: %v", in.GetName()) return &greetpb.HelloResponse{Message: "Hello " + in.GetName()}, } func main() { lis, err := net.Listen("tcp", ":50051") if err != nil { log.Fatalf("failed to listen: %v", err) } ss := grpc.NewServer() greetpb.RegisterGreeterServer(s, &server{}) reflection.Register(s) // Reflection für gRPCurl etc. aktivieren log.Printf("gRPC server listening at %v", lis.Addr()) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }
Starten Sie diesen Server: go run server/main.go
3. Richten Sie den Envoy-Proxy ein
Envoy wird sowohl unsere Gin-Anwendung als auch den gRPC-Dienst fronten und die gRPC-Web-Übersetzung übernehmen. Erstellen Sie envoy.yaml
:
static_resources: listeners: - name: listener_0 address: socket_address: protocol: TCP address: 0.0.0.0 port_value: 8080 # Port, an dem der Browser sich verbindet filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http codec_type: AUTO route_config: name: local_route virtual_hosts: - name: backend domains: ["*"] routes: - match: { prefix: "/greet.Greeter" } # gRPC-Web Service-Präfix route: { cluster: greeter_service, timeout: 0s } typed_per_filter_config: envoy.filters.http.grpc_web: {} - match: { prefix: "/" } # Catch-all für statische Dateien/APIs von Gin route: { cluster: gin_web_app } http_filters: - name: envoy.filters.http.grpc_web # gRPC-Web-Filter zuerst aktivieren typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb - name: envoy.filters.http.cors # Wichtig für die Browser-Sicherheit typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors allow_origin_string_match: - exact: "*" # Für die Produktion anpassen allow_methods: GET, PUT, DELETE, POST, OPTIONS allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web max_age: "1728000" - name: envoy.filters.http.router clusters: - name: greeter_service # gRPC-Backend connect_timeout: 0.25s type: LOGICAL_DNS dns_lookup_family: V4_ONLY lb_policy: ROUND_ROBIN load_assignment: cluster_name: greeter_service endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 127.0.0.1 # IP Ihres gRPC-Servers port_value: 50051 # Port Ihres gRPC-Servers - name: gin_web_app # Gin-Anwendung connect_timeout: 0.25s type: LOGICAL_DNS dns_lookup_family: V4_ONLY lb_policy: ROUND_ROBIN load_assignment: cluster_name: gin_web_app endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 127.0.0.1 # IP Ihres Gin-Servers port_value: 8081 # Port Ihres Gin-Servers
Um Envoy auszuführen (vorausgesetzt, Sie haben Docker installiert):
docker run --rm -it -p 8080:8080 -v $(pwd)/envoy.yaml:/etc/envoy/envoy.yaml envoyproxy/envoy:v1.27.0
Dies startet Envoy, das auf Port 8080
lauscht. Es leitet Anfragen an /greet.Greeter
an unseren gRPC-Server (Port 50051
) und alle anderen Anfragen an unsere Gin-App (Port 8081
) um.
4. Erstellen Sie den Gin Web Server
Gin dient zur Bereitstellung der statischen index.html
-Datei und anderer traditioneller HTTP-API-Endpunkte. Datei: web/main.go
package main import ( "log" "net/http" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() // Statische Dateien aus dem Verzeichnis 'public' bereitstellen r.Static("/static", "./public") r.LoadHTMLFiles("./public/index.html") r.GET("/", func(c *gin.Context) { c.HTML(http.StatusOK, "index.html", gin.H{}) }) log.Printf("Gin server listening on :8081") if err := r.Run(":8081"); err != nil { log.Fatalf("failed to run gin server: %v", err) } }
Erstellen Sie ein Verzeichnis public
und legen Sie index.html
darin ab.
public/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>gRPC-Web Greeter</title> <script src="/static/main.js" defer></script> </head> <body> <h1>gRPC-Web Greeter Example</h1> <input type="text" id="nameInput" placeholder="Enter your name"> <button id="greetButton">Say Hello</button> <p id="response"></p> </body> </html>
Starten Sie den Gin-Server: go run web/main.go
5. Erstellen Sie die Frontend-Anwendung
Das Frontend verwendet den generierten gRPC-Web-Client zur Kommunikation mit dem Envoy-Proxy.
Erstellen Sie public/main.js
(oder verwenden Sie TypeScript und kompilieren Sie es):
import { GreeterClient } from '../greeter_grpc_web_pb.js'; import { HelloRequest } from '../greeter_pb.js'; const greeterClient = new GreeterClient('http://localhost:8080', null, null); // Envoy-Adresse document.getElementById('greetButton').addEventListener('click', () => { const name = document.getElementById('nameInput').value; const request = new HelloRequest(); request.setName(name); greeterClient.sayHello(request, {}, (err, response) => { if (err) { console.error('Error calling SayHello:', err.code, err.message); document.getElementById('response').textContent = 'Error: ' + err.message; return; } document.getElementById('response').textContent = response.getMessage(); }); });
Um diesen clientseitigen Code auszuführen, benötigen Sie normalerweise einen Modul-Bundler wie Webpack oder Parcel, da er import
-Anweisungen verwendet. Für einen schnellen Test können Sie einen einfachen http-server
oder live-server
verwenden und die import
-Pfade vorübergehend anpassen oder ihn bündeln:
# Beispiel mit Parcel (installieren: npm install -g parcel-bundler) parcel build public/main.js --out-dir public --public-url /static
Stellen Sie sicher, dass Ihre public/main.js
unter /static/main.js
zugänglich ist, wie in Gin konfiguriert.
Anwendungsfall
Dieses Setup ist ideal für:
- Single-Page Applications (SPAs): React-, Vue-, Angular-Anwendungen können gRPC-Dienste direkt konsumieren, ohne eine benutzerdefinierte REST-API-Schicht.
- Interne Dashboards/Tools: Erstellen von administrativen Schnittstellen, die effizient mit Backend-Microservices kommunizieren.
- Hybride Anwendungen: Wo einige Teile des Frontends leistungsstarke Kommunikation mit gRPC-Diensten erfordern, während andere traditionelle HTTP-APIs verwenden (ebenfalls von Gin bereitgestellt).
- Reduzierung von Boilerplate: Durch die direkte Verwendung von Protobuf-Schemas erhalten Frontends automatisch generierten Client-Code, was die manuelle Entwicklung von API-Clients reduziert.
Vorteile dieses Ansatzes
- Leistung: Nutzung der binären Serialisierung von gRPC und HTTP/2 (zwischen Envoy und Backend) für eine effiziente Datenübertragung.
- Starke Typisierung: Protobuf bietet starke Typisierung vom Backend bis zum Frontend, was die Wartbarkeit verbessert und Laufzeitfehler reduziert.
- Einheitliches Schema: Eine einzige Wahrheitsquelle für Ihre API-Definition (Protobuf) sowohl für gRPC- als auch für gRPC-Web-Clients.
- Reduzierte Latenz: Eliminierung des Overhead für JSON-Parsing/-Marshalling im Vergleich zu REST-APIs.
- Vereinfachte Architektur: Weniger benutzerdefinierter Code für API-Gateways oder spezifische REST-Adapter.
Fazit
Durch die sorgfältige Orchestrierung von Gin als Webserver, einem Envoy-Proxy für die gRPC-Web-Übersetzung und einem gRPC-Backend ermöglichen wir Webbrowsern die direkte Interaktion mit hochperformanten gRPC-Diensten. Diese leistungsstarke Kombination erschließt das volle Potenzial von gRPC für Full-Stack-Architekturen und bietet starke Typisierung, verbesserte Leistung und eine optimierte Entwicklungserfahrung. Die Möglichkeit, gRPC vom Browser aus zu sprechen, verändert grundlegend, wie wir moderne Webanwendungen entwerfen und erstellen und bringt uns näher an ein wirklich durchgängiges gRPC-Ökosystem.