Mengakses gRPC dari Browser: Memahami gRPC-Web dan gRPC Gateway
Halo developer!
Jika Anda telah mengikuti perkembangan API modern, kemungkinan besar Anda sudah familiar dengan gRPC. Sebagai protokol Remote Procedure Call (RPC) yang dikembangkan oleh Google, gRPC menawarkan performa luar biasa, efisiensi bandwidth, dan kemudahan dalam mendefinisikan layanan menggunakan Protocol Buffers. Artikel sebelumnya di blog ini, gRPC: Membangun API Berperforma Tinggi dengan Protokol Modern, telah membahas dasar-dasar dan keunggulannya.
Namun, ada satu pertanyaan krusial yang sering muncul: Bagaimana kita bisa menggunakan gRPC di aplikasi web yang berjalan di browser?
Tantangan utamanya adalah browser tidak mendukung gRPC secara native. gRPC dibangun di atas HTTP/2, yang secara fundamental berbeda dengan cara browser berinteraksi dengan server melalui HTTP/1.1 (atau HTTP/2, tetapi dengan batasan tertentu untuk gRPC), dan juga masalah terkait permintaan lintas-asal (CORS) serta format data biner yang digunakan Protocol Buffers.
Jangan khawatir! Ada dua solusi utama yang populer untuk menjembatani kesenjangan ini: gRPC-Web dan gRPC Gateway. Keduanya memungkinkan aplikasi frontend berbasis browser Anda untuk berkomunikasi dengan backend gRPC, namun dengan pendekatan dan trade-off yang berbeda.
Dalam artikel ini, kita akan menyelami kedua solusi ini, memahami cara kerjanya, kapan harus menggunakan masing-masing, dan bagaimana mengimplementasikannya dalam proyek Anda. Siap? Mari kita mulai!
1. Memahami Tantangan gRPC di Lingkungan Browser
Sebelum membahas solusinya, penting untuk memahami mengapa gRPC tidak bisa langsung digunakan di browser.
📌 Masalah Utama:
- HTTP/2 Multiplexing & Trailer Headers: gRPC memanfaatkan fitur-fitur canggih HTTP/2 seperti multiplexing dan trailer headers untuk metadata dan status. Browser modern memang mendukung HTTP/2, tetapi mereka membatasi akses ke beberapa fitur low-level HTTP/2 yang penting untuk gRPC, terutama kemampuan untuk mengirim trailer headers setelah body respons.
- CORS (Cross-Origin Resource Sharing): Browser memberlakukan kebijakan keamanan ketat yang disebut CORS. Komunikasi gRPC, terutama dengan metode streaming yang berbeda, dapat memicu masalah CORS yang kompleks jika tidak ditangani dengan benar.
- Format Data Biner: gRPC menggunakan Protocol Buffers, format serialisasi biner yang sangat efisien. Browser secara native lebih familiar dengan format teks seperti JSON. Meskipun
ArrayBufferdapat menangani biner, integrasinya denganfetchAPI atauXMLHttpRequestuntuk gRPC stream menjadi rumit.
Singkatnya, browser dirancang untuk berinteraksi dengan API berbasis teks (JSON/XML) melalui HTTP/1.1 atau HTTP/2 yang lebih “sederhana”, bukan protokol biner yang sangat dioptimalkan seperti gRPC.
2. Solusi #1: gRPC-Web — Menjembatani Protokol
gRPC-Web adalah teknologi yang memungkinkan aplikasi browser untuk berkomunikasi dengan layanan gRPC dengan cara yang mirip seperti gRPC “asli”. Ini dilakukan dengan memodifikasi cara kerja gRPC di sisi client (browser) dan memerlukan proxy di sisi server.
Cara Kerja gRPC-Web
gRPC-Web pada dasarnya “menerjemahkan” panggilan gRPC dari browser ke format yang bisa dipahami oleh proxy (seperti Envoy atau grpc-web-proxy), yang kemudian meneruskannya ke backend gRPC Anda.
💡 Konsep Kunci:
- Client-side Library: Anda menggunakan library gRPC-Web khusus di browser (misalnya,
grpc-webuntuk JavaScript/TypeScript) yang mengimplementasikan protokol gRPC-Web. Library ini berkomunikasi melalui HTTP/1.1 atau HTTP/2 “normal” yang didukung browser. - Proxy Server: Proxy ini bertindak sebagai perantara. Ia menerima permintaan gRPC-Web dari browser, menerjemahkannya kembali ke protokol gRPC “penuh” (HTTP/2 dengan trailer headers), dan meneruskannya ke backend gRPC Anda. Sebaliknya, ia menerima respons gRPC dari backend, menerjemahkannya ke format gRPC-Web, dan mengirimkannya kembali ke browser.
✅ Keunggulan gRPC-Web:
- Keamanan Tipe (Type Safety): Karena menggunakan Protocol Buffers, Anda mendapatkan type safety di frontend maupun backend. Ini mengurangi kesalahan dan membuat refactoring lebih mudah.
- Efisiensi Bandwidth: Tetap mewarisi efisiensi Protocol Buffers yang ringkas.
- Mendukung Streaming: Mendukung unary (permintaan-respons tunggal) dan server-side streaming.
- Generasi Kode Otomatis: Client stub dapat digenerate otomatis dari definisi
.proto.
❌ Keterbatasan gRPC-Web:
- Membutuhkan Proxy: Selalu memerlukan proxy di antara browser dan backend gRPC Anda, menambah kompleksitas infrastruktur.
- Tidak Mendukung Client-side/Bidirectional Streaming: Hanya mendukung unary dan server-side streaming. Client-side dan bidirectional streaming (interaksi dua arah secara real-time) tidak didukung karena batasan HTTP/1.1 dan API browser.
Contoh Arsitektur gRPC-Web
+----------------+ HTTP/1.1 atau HTTP/2 +-------------------+ HTTP/2 +-------------------+
| Aplikasi Browser | <----------------------------> | gRPC-Web Proxy | <-----------------> | Backend gRPC |
| (JS/TS + grpc-web) | | (Envoy, grpc-web-proxy) | | (Go, Java, Node.js, dll.) |
+----------------+ +-------------------+ +-------------------+
Contoh Kode (Sederhana)
Misalkan Anda memiliki definisi proto Greeter dengan metode SayHello:
// proto/greeter.proto
syntax = "proto3";
package greeter;
option go_package = "./greeter";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
1. Generate Kode Client (TypeScript):
# Install tools jika belum
# npm install -g protoc-gen-ts
# npm install -g grpc-tools
# Generate JavaScript dan TypeScript definisi dari proto
protoc \
--plugin="protoc-gen-ts=./node_modules/.bin/protoc-gen-ts" \
--js_out="import_style=commonjs,binary:./src/generated" \
--ts_out="service=grpc-web:./src/generated" \
-I=./proto \
./proto/greeter.proto
2. Client-side (React/TypeScript):
// src/App.tsx
import React, { useState, useEffect } from 'react';
import { GreeterClient } from './generated/GreeterServiceClientPb';
import { HelloRequest, HelloReply } from './generated/greeter_pb';
import { grpc } from '@improbable-eng/grpc-web'; // grpc-web library
const client = new GreeterClient('http://localhost:8080', null, null); // Ganti dengan alamat proxy Anda
function App() {
const [name, setName] = useState('');
const [message, setMessage] = useState('');
const sayHello = () => {
const request = new HelloRequest();
request.setName(name);
client.sayHello(request, {}, (err, response) => {
if (err) {
console.error(err);
setMessage('Error: ' + err.message);
return;
}
if (response) {
setMessage(response.getMessage());
}
});
};
return (
<div>
<h1>gRPC-Web Client</h1>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Masukkan nama Anda"
/>
<button onClick={sayHello}>Kirim</button>
{message && <p>Respon dari gRPC: {message}</p>}
</div>
);
}
export default App;
3. Konfigurasi Proxy (Contoh Envoy):
Anda perlu mengonfigurasi proxy seperti Envoy untuk menerjemahkan permintaan gRPC-Web ke gRPC biasa. Konfigurasi ini biasanya melibatkan http_connection_manager dengan filter grpc_web.
# Bagian dari envoy.yaml
static_resources:
listeners:
- name: listener_0
address:
socket_address:
address: 0.0.0.0
port_value: 8080 # Port untuk gRPC-Web proxy
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: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: grpc_backend_cluster # Nama cluster backend gRPC Anda
# Jika Anda ingin mengaktifkan CORS
cors:
allow_origin_string_match:
- prefix: "*"
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
expose_headers: custom-header-1,grpc-status,grpc-message
max_age: "1728000"
http_filters:
- name: envoy.filters.http.grpc_web # Filter gRPC-Web
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
- name: envoy.filters.http.router
clusters:
- name: grpc_backend_cluster
connect_timeout: 0.25s
type: LOGICAL_DNS
lb_policy: ROUND_ROBIN
http2_protocol_options: {} # Penting: cluster backend harus HTTP/2
load_assignment:
cluster_name: grpc_backend_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1 # Alamat backend gRPC Anda
port_value: 50051 # Port backend gRPC Anda
3. Solusi #2: gRPC Gateway — RESTful API dari gRPC
gRPC Gateway mengambil pendekatan yang berbeda. Alih-alih menerjemahkan protokol gRPC, ia menghasilkan reverse proxy yang menerjemahkan permintaan HTTP/JSON RESTful menjadi panggilan gRPC. Ini berarti Anda mengekspos API yang terlihat seperti RESTful API biasa dari browser, tetapi di balik layar, ia memanggil layanan gRPC Anda.
Cara Kerja gRPC Gateway
gRPC Gateway bekerja dengan membaca definisi Protocol Buffers Anda dan menghasilkan reverse proxy yang dapat di-deploy sebagai layanan terpisah.
💡 Konsep Kunci:
- Proto Annotations: Anda menambahkan annotations (opsi khusus) ke definisi
.protoAnda untuk memetakan metode gRPC ke endpoint HTTP (misalnya,GET /v1/greeter/{name}). - Code Generation: Sebuah tool (misalnya,
protoc-gen-grpc-gateway) membaca annotations ini dan menghasilkan kode Go untuk reverse proxy. - Reverse Proxy: Proxy ini menerima permintaan HTTP/JSON dari browser, menerjemahkannya menjadi panggilan gRPC yang sesuai, memanggil backend gRPC Anda, dan kemudian menerjemahkan respons gRPC kembali ke HTTP/JSON sebelum mengirimkannya ke browser.
✅ Keunggulan gRPC Gateway:
- API Familiar: Frontend developer dapat berinteraksi dengan API seolah-olah itu adalah RESTful API biasa (HTTP/JSON), tanpa perlu library gRPC-Web khusus.
- Dukungan Penuh Browser: Karena ini adalah API HTTP/JSON standar, semua fitur browser (seperti
fetchAPI, client-side caching, dll.) dapat digunakan tanpa batasan. - Dokumentasi Otomatis: Dapat dengan mudah diintegrasikan dengan tool dokumentasi API standar (misalnya, OpenAPI/Swagger).
- Mendukung Semua Metode GPRC: Karena menerjemahkan ke panggilan gRPC, semua metode (unary, server-side, client-side, bidirectional streaming) dapat didukung oleh backend gRPC (meskipun client-side dan bidirectional streaming akan memiliki representasi HTTP yang lebih kompleks).
❌ Keterbatasan gRPC Gateway:
- Overhead JSON: Menggunakan JSON memiliki overhead serialisasi/deserialisasi dan ukuran payload yang lebih besar dibandingkan Protocol Buffers biner.
- Tidak Ada Type Safety End-to-End: Frontend berinteraksi dengan JSON, sehingga type safety yang ketat dari Protocol Buffers hanya berlaku antara gateway dan backend.
- Diperlukan Annotasi Proto: Membutuhkan penambahan annotations ke file
.protoAnda, yang dapat menambah kompleksitas. - Lebih Banyak Boilerplate: Proses code generation dan deployment gateway bisa terasa lebih rumit dibandingkan hanya menggunakan proxy gRPC-Web.
Contoh Arsitektur gRPC Gateway
+----------------+ HTTP/1.1 atau HTTP/2 +-------------------+ HTTP/2 +-------------------+
| Aplikasi Browser | <----------------------------> | gRPC Gateway | <-----------------> | Backend gRPC |
| (JS/TS + fetch) | | (Go reverse proxy) | | (Go, Java, Node.js, dll.) |
+----------------+ +-------------------+ +-------------------+
Contoh Kode (Sederhana)
Kita akan menggunakan definisi proto yang sama, tetapi dengan annotations HTTP:
// proto/greeter.proto
syntax = "proto3";
package greeter;
option go_package = "./greeter";
import "google/api/annotations.proto"; // Penting: Import annotations
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
get: "/v1/greeter/{name}" // Map ke HTTP GET request
};
}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
1. Generate Kode Gateway (Go):
# Install tools jika belum
# go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
# go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
# go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# Pastikan Anda memiliki file google/api/annotations.proto di path include Anda
# Biasanya bisa didapatkan dari github.com/googleapis/googleapis
protoc \
-I. \
-I$(go env GOPATH)/src \ # Atau path lain ke googleapis
--go_out=paths=source_relative:. \
--go-grpc_out=paths=source_relative:. \
--grpc-gateway_out=paths=source_relative:. \
./proto/greeter.proto
2. Server-side Gateway (Go):
// main.go (untuk gRPC Gateway)
package main
import (
"context"
"log"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
gw "your_module_path/proto" // Ganti dengan path modul Anda
)
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// Opsi untuk koneksi ke backend gRPC
grpcServerEndpoint := "localhost:50051" // Alamat backend gRPC Anda
opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
// Buat Mux untuk gRPC Gateway
gwmux := runtime.NewServeMux()
err := gw.RegisterGreeterHandlerFromEndpoint(ctx, gwmux, grpcServerEndpoint, opts)
if err != nil {
return err
}
// Konfigurasi CORS (penting untuk browser)
corsMux := http.NewServeMux()
corsMux.Handle("/", gwmux) // Handle permintaan gateway
// Tambahkan middleware CORS
handler := func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*") // Sesuaikan dengan domain frontend Anda
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-Grpc-Web") // Tambahkan header gRPC-Web jika diperlukan
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
h.ServeHTTP(w, r)
})
}
log.Printf("gRPC Gateway serving HTTP/JSON on port 8080")
return http.ListenAndServe(":8080", handler(corsMux))
}
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
3. Client-side (React/TypeScript dengan fetch):
// src/App.tsx
import React, { useState } from 'react';
function App() {
const [name, setName] = useState('');
const [message, setMessage] = useState('');
const sayHello = async () => {
try {
// Panggil endpoint RESTful yang dihasilkan oleh gRPC Gateway
const response = await fetch(`http://localhost:8080/v1/greeter/${name}`);
const data = await response.json();
setMessage(data.message);
} catch (error) {
console.error('Error fetching data:', error);
setMessage('Error: ' + error.message);
}
};
return (
<div>
<h1>gRPC Gateway Client (RESTful)</h1>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Masukkan nama Anda"
/>
<button onClick={sayHello}>Kirim</button>
{message && <p>Respon dari gRPC Gateway: {message}</p>}
</div>
);
}
export default App;
4. Kapan Menggunakan gRPC-Web vs. gRPC Gateway?
Memilih antara gRPC-Web dan gRPC Gateway tergantung pada prioritas dan kebutuhan proyek Anda.
🎯 Pilih gRPC-Web jika:
- Type Safety End-to-End adalah Prioritas: Anda ingin memanfaatkan type safety Protocol Buffers secara penuh di frontend dan backend.
- Efisiensi Bandwidth dan Performa Tinggi: Anda memprioritaskan ukuran payload yang ringkas dan efisiensi komunikasi biner.
- Mendukung Server-side Streaming: Anda membutuhkan server-side streaming untuk real-time updates dari server ke browser.
- Tim Frontend Nyaman dengan gRPC: Tim frontend Anda bersedia dan mampu bekerja dengan library gRPC-Web dan konsep gRPC.
- Infrastruktur Mendukung Proxy: Anda memiliki atau bersedia mengimplementasikan proxy seperti Envoy.
⚠️ Pilih gRPC Gateway jika:
- Kompatibilitas RESTful adalah Kunci: Anda ingin mengekspos API yang familiar dan mudah digunakan oleh frontend developer yang terbiasa dengan REST/JSON.
- Integrasi dengan Ekosistem HTTP/JSON: Anda perlu mengintegrasikan API Anda dengan tool pihak ketiga yang hanya mendukung HTTP/JSON (misalnya, gateway API lainnya, monitoring, logging, analytics).
- Fleksibilitas HTTP Penuh: Anda membutuhkan fleksibilitas penuh dari HTTP, termasuk caching browser, load balancing berbasis HTTP, dan integrasi OpenAPI/Swagger.
- Tidak Membutuhkan Client-side/Bidirectional Streaming dari Browser: Jika streaming yang Anda butuhkan hanya satu arah dari server, atau Anda tidak memerlukan streaming sama sekali dari browser.
- Mengurangi Kurva Pembelajaran Frontend: Mengurangi kurva pembelajaran untuk frontend developer karena mereka tidak perlu mempelajari gRPC secara mendalam.
5. Best Practices & Pertimbangan Lain
- Pikirkan CORS: Apa pun pilihan Anda, konfigurasi CORS yang benar sangat penting untuk menghindari masalah keamanan di browser.
- Monitoring dan Logging: Pastikan Anda memiliki monitoring dan logging yang memadai di proxy atau gateway Anda untuk melacak performa dan mendeteksi masalah.
- Keamanan: Implementasikan TLS/SSL untuk semua komunikasi, baik antara browser-proxy/gateway maupun proxy/gateway-backend gRPC.
- Deployment: Pertimbangkan bagaimana Anda akan mendeploy proxy atau gateway ini. Apakah itu sebagai sidecar di Kubernetes, service terpisah, atau terintegrasi dengan load balancer Anda?
- Dokumentasi API: Untuk gRPC Gateway, manfaatkan kemampuan untuk menghasilkan dokumentasi OpenAPI/Swagger dari definisi proto Anda. Ini sangat membantu frontend developer.
Kesimpulan
gRPC adalah teknologi API yang kuat dan efisien, tetapi penggunaannya di browser memerlukan jembatan. gRPC-Web dan gRPC Gateway adalah dua solusi utama yang ampuh, masing-masing dengan kekuatan dan kelemahannya sendiri.
- gRPC-Web mempertahankan sebagian besar manfaat gRPC (efisiensi, type safety) hingga ke browser, tetapi memerlukan proxy dan memiliki batasan streaming tertentu.
- gRPC Gateway menyediakan API yang familiar (RESTful HTTP/JSON) untuk browser, menjadikannya pilihan yang lebih mudah diintegrasikan dengan ekosistem web standar, dengan trade-off pada overhead JSON dan type safety end-to-end.
Memilih yang tepat tergantung pada prioritas proyek Anda: apakah itu performa dan type safety maksimal, atau kemudahan integrasi dan familiaritas bagi frontend developer. Dengan pemahaman yang tepat tentang kedua pendekatan ini, Anda sekarang memiliki tool untuk membuat backend gRPC Anda dapat diakses oleh semua aplikasi web modern.
Selamat membangun API yang cepat dan handal!