The gRPC Gateway is a plugin for the Envoy proxy that translates RESTful HTTP API calls into gRPC calls.

Let’s see it in action. Imagine you have a simple gRPC service, say a KeyValue service with Get and Put methods. You’d define this in a .proto file:

syntax = "proto3";

package mypackage;

message GetRequest {
  string key = 1;
}

message GetResponse {
  string value = 1;
}

message PutRequest {
  string key = 1;
  string value = 1;
}

message PutResponse {}

service KeyValue {
  rpc Get (GetRequest) returns (GetResponse);
  rpc Put (PutRequest) returns (PutResponse);
}

Now, you want to expose this service over HTTP. Instead of writing a separate HTTP server or manually mapping endpoints, you use the gRPC Gateway. You’d generate Go code for your gRPC service and then use the protoc-gen-grpc-gateway plugin to generate a Go file that handles the HTTP to gRPC translation. This generated file contains functions that, when hooked up to an HTTP server (like net/http in Go), will take an incoming HTTP request, parse its body into the appropriate gRPC request message, call your gRPC service implementation, and then serialize the gRPC response back into an HTTP response.

Here’s a simplified example of how you might set up the Go server:

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"

	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

	pb "path/to/your/generated/pb" // Your generated gRPC code
)

func main() {
	ctx := context.Background()

	// Create a client connection to the gRPC server.
	// Assume your gRPC server is running on localhost:50051
	conn, err := grpc.DialContext(ctx, "localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalln("Failed to dial server:", err)
	}
	defer conn.Close()

	// Create a multiplexer for our HTTP server.
	mux := runtime.NewServeMux()

	// Register the KeyValue handler from the gRPC Gateway.
	// This tells the mux to route HTTP requests to the correct gRPC calls.
	if err := pb.RegisterKeyValueHandler(ctx, mux, conn); err != nil {
		log.Fatalln("Failed to register gateway:", err)
	}

	// Start the HTTP server.
	log.Println("Serving HTTP on :8080")
	if err := http.ListenAndServe(":8080", mux); err != nil {
		log.Fatalln("Failed to start server:", err)
	}
}

In this setup, when an HTTP request comes into :8080, the mux (which is the runtime.ServeMux from the gRPC Gateway) inspects the URL path and method. If it matches a route registered by pb.RegisterKeyValueHandler, it will:

  1. Determine the corresponding gRPC method (e.g., /mypackage.KeyValue/Get).
  2. Read the HTTP request body (e.g., JSON) and unmarshal it into the appropriate gRPC request message (e.g., mypackage.GetRequest).
  3. Call the actual gRPC service implementation running on localhost:50051 with this message.
  4. Take the gRPC response message (e.g., mypackage.GetResponse) and marshal it into an HTTP response body (e.g., JSON).

This means you can interact with your gRPC service using familiar tools like curl:

# To call the Put method
curl -X POST \
  http://localhost:8080/v1/items \
  -H 'Content-Type: application/json' \
  -d '{"key": "mykey", "value": "myvalue"}'

# To call the Get method
curl http://localhost:8080/v1/items/mykey

Note that the path mapping (e.g., /v1/items mapping to the KeyValue service) is often configured via annotations in your .proto file. The gRPC Gateway reads these annotations to understand how to map HTTP paths and methods to gRPC calls.

The core problem the gRPC Gateway solves is bridging the gap between the RPC-centric world of gRPC and the widely adopted RESTful HTTP world. It allows you to define your API once in Protocol Buffers and expose it through both gRPC and HTTP, significantly reducing development effort and ensuring API consistency. Internally, it relies on a sophisticated routing and marshaling layer that understands how to translate between HTTP concepts (like URL paths, query parameters, request bodies, headers) and gRPC concepts (messages, fields, service methods). The runtime.ServeMux is the heart of this, acting as a reverse proxy that can dynamically dispatch incoming HTTP requests to the correct gRPC stub based on the registered handlers.

What most people don’t realize is that the gRPC Gateway can also handle streaming RPCs, both client-side and server-side. For server-streaming RPCs, it effectively buffers the stream of gRPC messages and sends them as a single, chunked HTTP response. For client-streaming RPCs, it reads the incoming HTTP request body chunk by chunk, sending each chunk as a message to the gRPC client stream. This makes it possible to use HTTP for advanced streaming patterns that are common in gRPC.

The next step is to explore how to customize the HTTP response status codes and headers based on gRPC status codes and metadata.

Want structured learning?

Take the full Etcd course →