GRPC GRPC-WEB GRPC-GATEWAY API WEB-DEVELOPMENT FRONTEND BACKEND NETWORKING PROTOCOL PROXY FULLSTACK BEST-PRACTICES INTEROPERABILITY ARCHITECTURE DEVOPS

Mengakses gRPC dari Browser: Memahami gRPC-Web dan gRPC Gateway

⏱️ 18 menit baca
👨‍💻

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:

  1. 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.
  2. 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.
  3. Format Data Biner: gRPC menggunakan Protocol Buffers, format serialisasi biner yang sangat efisien. Browser secara native lebih familiar dengan format teks seperti JSON. Meskipun ArrayBuffer dapat menangani biner, integrasinya dengan fetch API atau XMLHttpRequest untuk 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:

Keunggulan gRPC-Web:

Keterbatasan gRPC-Web:

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:

Keunggulan gRPC Gateway:

Keterbatasan gRPC Gateway:

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:

⚠️ Pilih gRPC Gateway jika:

5. Best Practices & Pertimbangan Lain

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.

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!

🔗 Baca Juga