Skip to content

gRPC Gateway — Bridging HTTP and gRPC Services

DodaTech Updated 2026-06-28 5 min read

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

  1. Why cannot browsers call gRPC services directly?
  2. What is the difference between gRPC-Web and gRPC transcoding?
  3. How does the gateway handle bidirectional streaming over HTTP?
  4. What HTTP status code maps to a gRPC UNAVAILABLE status?
  5. 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

What is gRPC-Web?

gRPC-Web is a protocol that enables browser clients to call gRPC services through an HTTP/1.1 gateway proxy that translates between HTTP and HTTP/2 gRPC.

Do I need a separate gateway for gRPC?

Not necessarily. Most API gateways support gRPC passthrough or transcoding. Kong, Envoy, and NGINX all have gRPC support built in or via plugins.

Can I use REST and gRPC simultaneously?

Yes. A common pattern is to use a gateway that detects the protocol from the Content-Type header and routes accordingly.

How does error handling work with gRPC gateway?

gRPC status codes are translated to HTTP status codes at the gateway. Each gRPC code has a recommended HTTP mapping defined by the gRPC HTTP spec.

What are the performance implications of a gRPC gateway?

The gateway adds minimal latency (1-5ms) for JSON-to-protobuf conversion but enables the benefits of gRPC internally while supporting REST clients externally.

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