gRPC-Web
browser ทำไม่ได้
หัวข้อที่มีชื่อว่า “browser ทำไม่ได้”gRPC แบบ native ต้องควบคุม HTTP/2 frame อย่างละเอียด — trailer, flow control และ framing ดิบ ๆ ที่เราเห็นในโมดูล foundations แต่ browser fetch API ตั้งใจซ่อนทั้งหมดนั้นไว้ JavaScript ในหน้าเว็บเปิด raw HTTP/2 stream แล้วเขียน gRPC frame ลงไปไม่ได้เลย
ดังนั้น browser เรียก gRPC server ตรง ๆ ไม่ได้ นี่ไม่ใช่ bug ที่ config แก้ได้ แต่เป็นขอบเขตของสิ่งที่ browser API เปิดให้ทำ
gRPC-Web: dialect ที่เป็นมิตรกับ browser
หัวข้อที่มีชื่อว่า “gRPC-Web: dialect ที่เป็นมิตรกับ browser”gRPC-Web คือ variant ของ protocol ที่ออกแบบให้อยู่ในสิ่งที่ browser ทำได้ โดยขน protobuf message เดิม แต่ frame ให้รอดผ่าน HTTP request ปกติ — รวมถึง encode trailer (ที่ gRPC ใช้ใส่ status code) ลงไปใน response body เพราะ browser อ่าน HTTP/2 trailer ไม่ได้
จุดที่ต้องระวัง: request แบบ gRPC-Web ไม่ใช่ byte ชุดเดียวกับ request แบบ native gRPC ต้องมีอะไรสักอย่างมาแปลระหว่างสองแบบนี้
คุณต้องมี proxy
หัวข้อที่มีชื่อว่า “คุณต้องมี proxy”ตัวแปลนั้นอยู่ระหว่าง browser กับ gRPC service ของคุณ:
flowchart LR browser["browser gRPC-Web client"] -->|gRPC-Web over HTTP/1.1 or /2| proxy["proxy (Envoy / in-process)"] proxy -->|native gRPC over HTTP/2| backend["gRPC backend"] backend --> proxy --> browser
สองวิธีที่พบบ่อยในการรัน proxy นั้น:
- Envoy พร้อม gRPC-Web filter — edge proxy เฉพาะทางที่แปล gRPC-Web เป็น native gRPC เหมาะเมื่อคุณรัน Envoy อยู่แล้ว
- In-process handler — หลาย stack (grpc-web wrapper ของ Go และอื่น ๆ) ให้ gRPC server รับ gRPC-Web ได้ตรง ๆ โดยไม่ต้องมี proxy แยก
ฝั่ง client คุณใช้ gRPC-Web stub ที่ generate มาแทน native stub:
// Browser (gRPC-Web) — generated client talks to the proxy URLimport { UserServiceClient } from './gen/user.client';import { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport';
const transport = new GrpcWebFetchTransport({ baseUrl: 'https://api.example.com' });const client = new UserServiceClient(transport);
const { response } = await client.getUser({ id: 42 });console.log(response.name);// Server-side: let the same gRPC server also accept gRPC-WebgrpcServer := grpc.NewServer()userv1.RegisterUserServiceServer(grpcServer, &server{})
wrapped := grpcweb.WrapServer(grpcServer) // accepts gRPC-Web requestshttp.ListenAndServe(":8080", wrapped)# Python has no browser runtime; front Python gRPC services with# Envoy configured with the grpc_web filter to serve browsers.# envoy.yaml (excerpt):# http_filters:# - name: envoy.filters.http.grpc_web# - name: envoy.filters.http.routerConnect: ทางเลือกที่ทันสมัยกว่า
หัวข้อที่มีชื่อว่า “Connect: ทางเลือกที่ทันสมัยกว่า”Connect (จาก Buf) คือแนวทางใหม่ที่พูดได้ 3 protocol จาก server เดียว: Connect protocol ของตัวเอง, gRPC และ gRPC-Web โดย Connect protocol ทำงานบน HTTP/1.1 ธรรมดาและขน JSON ได้ ทำให้ browser — หรือแม้แต่ curl — เรียกได้โดยไม่ต้องมี proxy เลย ขณะที่ backend service ยังใช้ gRPC อยู่ สำหรับงานใหม่ที่หันหน้าเข้า browser Connect ตัด friction ของ gRPC-Web ออกไปเกือบหมด
มีผลต่อการออกแบบยังไง
หัวข้อที่มีชื่อว่า “มีผลต่อการออกแบบยังไง”- วางแผน proxy ตั้งแต่วันแรก ถ้ามี browser เป็น client gRPC-Web ไม่ใช่ glue ที่ค่อยเพิ่มทีหลัง แต่เป็นทางเดียวที่เข้าได้
- streaming ถูกจำกัด gRPC-Web รองรับ unary และ server-streaming ได้ดี แต่ client-streaming และ bidirectional streaming ยังใช้บน browser ไม่ได้ทั่วไป
- พิจารณา Connect สำหรับ browser-facing API ที่เริ่มใหม่ — server เดียว, 3 protocol, ไม่ต้องมี proxy แยก