Day 123 — grpc-gateway / REST Transcoding¶
Month 5 · Week 2 · ⬅ Day 122 · Day 124 ➡ · Journal index
🎯 Learning Objective¶
Expose a gRPC service as a RESTful JSON API using grpc-gateway, and understand how HTTP requests are transcoded into RPC calls.
📚 Topics¶
google.api.httpannotations and path templates ({id})protoc-gen-grpc-gatewayreverse proxy +runtime.ServeMux- HTTP method/path → RPC mapping; JSON ↔ protobuf
📖 Reading / Sources¶
- grpc-gateway docs
-
google/api/http.protoannotations - grpc-gateway runtime
ServeMux - Transcoding HTTP/JSON to gRPC (Google AIP)
📝 Notes¶
- grpc-gateway generates a reverse proxy: an HTTP/JSON request comes in, it transcodes it into a gRPC call to your backend, then transcodes the response back to JSON. One service definition serves both gRPC and REST clients → [[grpc-gateway]] [[transcoding]].
- You annotate RPCs in the
.protowithgoogle.api.http:option (google.api.http) = { get: "/v1/users/{id}" };. The{id}path template binds a URL segment to a request field;body: "*"maps the JSON body to the request message for POST/PUT → [[path-template]]. - The generated
RegisterXxxHandlerFromEndpointwires aruntime.ServeMuxto a backend address; you serve it with an ordinaryhttp.Server. Often the gateway and gRPC server run in the same binary, the gateway dialinglocalhost→ [[reverse-proxy]]. - Errors transcode too: a gRPC
codes.NotFoundbecomes HTTP 404 via the gateway's code→status table (the same mapping I rebuilt on Day 118) → [[status-codes]]. - The matcher tries routes in order, compares method + segment count, and extracts
{name}segments into request fields. Literal segments must match exactly; a miss is a 404 → [[routing]]. - grpc-gateway can also emit an OpenAPI/Swagger spec (
protoc-gen-openapiv2) from the same annotations, so REST consumers get docs for free. - Trade-off: the gateway adds a marshal/unmarshal hop and loses streaming semantics for unary-style REST. Use it for browser/3rd-party REST clients; keep gRPC for service-to-service.
💻 Code Examples¶
// user.proto — the annotation that drives transcoding
service Users {
rpc GetUser(GetUserRequest) returns (User) {
option (google.api.http) = { get: "/v1/users/{id}" };
}
rpc CreateUser(CreateUserRequest) returns (User) {
option (google.api.http) = { post: "/v1/users" body: "*" };
}
}
// main.go — run the gateway in front of the gRPC backend
mux := runtime.NewServeMux()
err := userpb.RegisterUsersHandlerFromEndpoint(ctx, mux, "localhost:50051",
[]grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())})
http.ListenAndServe(":8080", mux) // GET /v1/users/42 -> GetUser{id:"42"}
Stdlib rebuild of the matcher (
/v1/users/{id}→ params → JSON, no protoc):examples/month-05/transcoding/main.go· Run:go run ./examples/month-05/transcoding
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
Parse/Match grpc-gateway path templates |
✅ | exercises/month-05/week-2/pathtemplate |
Extract {id}/{book_id} into a params map |
✅ | exercises/month-05/week-2/pathtemplate |
🐛 Mistakes Made¶
- Forgot
body: "*"on the POST and the request body never populated the message — the gateway only maps the body when told to. - Assumed
{id}could span multiple path segments; by default it matches a single segment (you need{id=**}for greedy matching).
❓ Open Questions¶
- How are query parameters mapped to nested request fields, and how does
?field.subfield=notation work?
🧠 Active Recall (answer without looking)¶
- Q: In
get: "/v1/users/{id}", what does{id}do?
A
It's a path-template binding: the matching URL segment is extracted and assigned to the request message's `id` field before the RPC is called. By default it matches exactly one segment.- Q: A gRPC backend returns
codes.NotFound. What HTTP status does the gateway send?
A
404 — grpc-gateway maps gRPC status codes to HTTP status codes (NotFound→404, InvalidArgument→400, Unauthenticated→401, etc.).🪶 Feynman Reflection¶
grpc-gateway is a translator standing between REST clients and a gRPC server. A browser says "GET /v1/users/42"; the translator reads the path template, pulls "42" into a GetUserRequest{id:"42"}, makes the gRPC call, takes the protobuf reply, and reads it back out as JSON. One .proto, two front doors.
🕳️ Knowledge Gaps¶
- Query-parameter → field mapping rules, and custom error/marshaler hooks on
ServeMux.
✅ Summary¶
I understand how google.api.http annotations transcode HTTP+JSON into gRPC calls, how path templates bind URL segments to fields, and the unary-vs-streaming trade-off of fronting gRPC with REST.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 124: server reflection and poking services with
grpcurl.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦🟦⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: feat(examples): grpc-gateway REST transcoding matcher (day 123)