gRPC Gateway — Bridging HTTP and gRPC Services
In this tutorial, you'll learn about gRPC Gateway. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
A gRPC gateway translates HTTP REST requests into gRPC calls, enabling browser and mobile clients to consume gRPC services without native gRPC support.
What You'll Learn
By the end of this lesson, you will configure gRPC-Web, set up HTTP-to-gRPC transcoding, handle streaming responses, and manage protocol conversion at the gateway level.
Why It Matters
gRPC offers high-performance, strongly-typed APIs but browsers cannot natively speak gRPC. A gateway bridges this gap, letting you use gRPC internally while supporting REST clients.
Real-World Use
Durga Antivirus Pro uses gRPC for internal service communication and a gRPC gateway to expose the same APIs as REST endpoints for web and mobile clients.
gRPC Gateway Architecture
flowchart LR
Client[HTTP Client]
Gateway[gRPC Gateway]
Service[gRPC Service]
Client-->|HTTP JSON|Gateway
Gateway-->|HTTP/2 Protobuf|Service
Service-->|Response|Gateway
Gateway-->|JSON Response|Client
gRPC-Web Configuration
gRPC-Web allows browser clients to communicate with gRPC services through a gateway.
from grpc_web import GatewayService
import grpc
from concurrent import futures
import time
class GRPCWebGateway:
def __init__(self, target_host: str = "localhost",
target_port: int = 50051,
gateway_port: int = 8080):
self.target = f"{target_host}:{target_port}"
self.gateway_port = gateway_port
self.server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
def add_service(self, servicer_class, stub_class):
servicer = servicer_class()
self.server.add_generic_rpc_handlers(
(GatewayService(servicer, stub_class),)
)
def start(self):
self.server.add_insecure_port(
f"[::]:{self.gateway_port}"
)
self.server.start()
print(f"gRPC gateway listening on {self.gateway_port}")
print(f"Proxying to gRPC server at {self.target}")
gateway = GRPCWebGateway()
gateway.start()
try:
while True:
time.sleep(86400)
except KeyboardInterrupt:
gateway.server.stop(0)
HTTP-to-gRPC Transcoding
Transcoding maps REST endpoints to gRPC method calls automatically.
from google.protobuf import json_format
import json
from typing import Dict, Any, Optional
class TranscodingRule:
def __init__(self, http_method: str, http_path: str,
grpc_service: str, grpc_method: str):
self.http_method = http_method
self.http_path = http_path
self.grpc_service = grpc_service
self.grpc_method = grpc_method
class TranscodingGateway:
def __init__(self):
self.rules: list[TranscodingRule] = []
def add_rule(self, rule: TranscodingRule):
self.rules.append(rule)
def find_rule(self, http_method: str, http_path: str
) -> Optional[TranscodingRule]:
for rule in self.rules:
if (rule.http_method == http_method
and rule.http_path == http_path):
return rule
return None
def translate_request(self, http_method: str,
http_path: str,
body: Optional[Dict]) -> Optional[Dict]:
rule = self.find_rule(http_method, http_path)
if not rule:
return None
return {
"service": rule.grpc_service,
"method": rule.grpc_method,
"payload": body or {}
}
gateway = TranscodingGateway()
gateway.add_rule(TranscodingRule(
"GET", "/v1/users/{id}",
"UserService", "GetUser"
))
gateway.add_rule(TranscodingRule(
"POST", "/v1/users",
"UserService", "CreateUser"
))
result = gateway.translate_request(
"GET", "/v1/users/42", None
)
print(json.dumps(result, indent=2))
Streaming Response Handling
gRPC supports server-side, client-side, and bidirectional streaming. The gateway must translate these to appropriate HTTP patterns.
import asyncio
from typing import AsyncGenerator, Callable, Any
class StreamingGateway:
def __init__(self):
self.stream_handlers: Dict[str, Callable] = {}
def register_stream(self, path: str,
handler: Callable):
self.stream_handlers[path] = handler
async def handle_server_stream(
self, path: str, params: Dict
) -> AsyncGenerator[Dict, None]:
handler = self.stream_handlers.get(path)
if not handler:
yield {"error": "not found"}
return
async for chunk in handler(params):
yield chunk
async def handle_client_stream(
self, path: str,
chunks: AsyncGenerator[Dict, None]
) -> Dict:
handler = self.stream_handlers.get(path)
if not handler:
return {"error": "not found"}
return await handler(chunks)
gateway = StreamingGateway()
async def scan_updates(params: Dict) -> AsyncGenerator[Dict, None]:
paths = ["/tmp/file1", "/tmp/file2", "/tmp/file3"]
for path in paths:
yield {"file": path, "status": "scanning"}
await asyncio.sleep(0.1)
yield {"status": "complete"}
gateway.register_stream("/v1/scan/updates", scan_updates)
Protocol Buffers and Gateway
The gateway needs to understand protobuf message definitions to perform transcoding.
from google.protobuf import descriptor_pb2
from google.protobuf import json_format
import json
class ProtoRegistry:
def __init__(self):
self.descriptors = {}
def register(self, service_name: str,
proto_file: str):
self.descriptors[service_name] = proto_file
def json_to_proto(self, service: str,
method: str,
json_data: Dict) -> bytes:
descriptor = self.descriptors.get(service)
if not descriptor:
raise ValueError(f"Unknown service: {service}")
message_type = self._resolve_message(
descriptor, service, method, "request"
)
proto_msg = json_format.Parse(
json.dumps(json_data), message_type()
)
return proto_msg.SerializeToString()
def proto_to_json(self, service: str,
method: str,
proto_bytes: bytes) -> Dict:
descriptor = self.descriptors.get(service)
message_type = self._resolve_message(
descriptor, service, method, "response"
)
proto_msg = message_type()
proto_msg.ParseFromString(proto_bytes)
return json.loads(
json_format.MessageToJson(proto_msg)
)
registry = ProtoRegistry()
Common Mistakes
Mistake 1: Ignoring gRPC Status Codes
HTTP status codes do not directly map to gRPC status codes. Always translate properly.
Mistake 2: Not Handling Streaming Timeouts
Streaming connections through a gateway need idle timeouts to prevent resource leaks.
Mistake 3: Forgetting CORS for gRPC-Web
gRPC-Web requires CORS headers just like REST APIs. Configure these at the gateway.
Mistake 4: Mixing gRPC and REST on Same Port
Use separate ports or clear path prefixes to distinguish gRPC and REST traffic.
Mistake 5: Overlooking Payload Size Limits
gRPC has a default 4MB message size limit. Configure both the gateway and service appropriately.
Practice Questions
- Why cannot browsers call gRPC services directly?
- What is the difference between gRPC-Web and gRPC transcoding?
- How does the gateway handle bidirectional streaming over HTTP?
- What HTTP status code maps to a gRPC UNAVAILABLE status?
- Why must the gateway understand protobuf descriptors?
Challenge
Build a gRPC gateway that proxies a health check service, translating a REST GET /health endpoint to the gRPC Health/Watch method.
FAQ
Mini Project
Build a gRPC gateway that exposes a UserService (GetUser, CreateUser, ListUsers) as REST endpoints, with protobuf-to-JSON transcoding, gRPC-Web support, and proper error code translation.
What's Next
Learn about GraphQL Gateway for flexible API composition, or explore WebSocket Gateway for real-time bidirectional communication.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro