Adding gRPC-Gateway annotations to an existing proto file

Now that we’ve got a working Go gRPC server, we need to add the gRPC-Gateway annotations.

The annotations define how gRPC services map to the JSON request and response. When using protocol buffers, each RPC must define the HTTP method and path using the google.api.http annotation.

So we will need to add the google/api/http.proto import to the proto file. We also need to add the HTTP->gRPC mapping we want. In this case, we’re mapping POST /v1/example/echo to our SayHello RPC.

  1. syntax = "proto3";
  2. package helloworld;
  3. import "google/api/annotations.proto";
  4. // Here is the overall greeting service definition where we define all our endpoints
  5. service Greeter {
  6. // Sends a greeting
  7. rpc SayHello (HelloRequest) returns (HelloReply) {
  8. option (google.api.http) = {
  9. post: "/v1/example/echo"
  10. body: "*"
  11. };
  12. }
  13. }
  14. // The request message containing the user's name
  15. message HelloRequest {
  16. string name = 1;
  17. }
  18. // The response message containing the greetings
  19. message HelloReply {
  20. string message = 1;
  21. }

See a_bit_of_everything.proto for examples of more annotations you can add to customize gateway behavior.

Generating the gRPC-Gateway stubs

Now that we’ve got the gRPC-Gateway annotations added to the proto file, we need to use the gRPC-Gateway generator to generate the stubs.

Using buf

We’ll need to add the gRPC-Gateway generator to the generation configuration:

  1. version: v1
  2. plugins:
  3. - name: go
  4. out: proto
  5. opt: paths=source_relative
  6. - name: go-grpc
  7. out: proto
  8. opt: paths=source_relative,require_unimplemented_servers=false
  9. - name: grpc-gateway
  10. out: proto
  11. opt: paths=source_relative

We’ll also need to add the googleapis dependency to our buf.yaml file:

  1. version: v1
  2. name: buf.build/myuser/myrepo
  3. deps:
  4. - buf.build/googleapis/googleapis

Then we need to run buf mod update to select a version of the dependency to use.

And that’s it! Now if you run:

  1. $ buf generate

It should produce a *.gw.pb.go file.

Using protoc

Before we can generate the stubs with protoc, we need to copy some dependencies into our proto file structure. Copy a subset of the googleapis from the official repository to your local proto file structure. It should look like this afterwards:

  1. proto
  2. ├── google
  3. └── api
  4. ├── annotations.proto
  5. └── http.proto
  6. └── helloworld
  7. └── hello_world.proto

Now we need to add the gRPC-Gateway generator to the protoc invocation:

  1. $ protoc -I ./proto \
  2. --go_out ./proto --go_opt paths=source_relative \
  3. --go-grpc_out ./proto --go-grpc_opt paths=source_relative \
  4. --grpc-gateway_out ./proto --grpc-gateway_opt paths=source_relative \
  5. ./proto/helloworld/hello_world.proto

This should generate a *.gw.pb.go file.

We also need to add and serve the gRPC-Gateway mux in our main.go file.

  1. package main
  2. import (
  3. "context"
  4. "log"
  5. "net"
  6. "net/http"
  7. "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
  8. "google.golang.org/grpc"
  9. "google.golang.org/grpc/credentials/insecure"
  10. helloworldpb "github.com/myuser/myrepo/proto/helloworld"
  11. )
  12. type server struct{
  13. helloworldpb.UnimplementedGreeterServer
  14. }
  15. func NewServer() *server {
  16. return &server{}
  17. }
  18. func (s *server) SayHello(ctx context.Context, in *helloworldpb.HelloRequest) (*helloworldpb.HelloReply, error) {
  19. return &helloworldpb.HelloReply{Message: in.Name + " world"}, nil
  20. }
  21. func main() {
  22. // Create a listener on TCP port
  23. lis, err := net.Listen("tcp", ":8080")
  24. if err != nil {
  25. log.Fatalln("Failed to listen:", err)
  26. }
  27. // Create a gRPC server object
  28. s := grpc.NewServer()
  29. // Attach the Greeter service to the server
  30. helloworldpb.RegisterGreeterServer(s, &server{})
  31. // Serve gRPC server
  32. log.Println("Serving gRPC on 0.0.0.0:8080")
  33. go func() {
  34. log.Fatalln(s.Serve(lis))
  35. }()
  36. // Create a client connection to the gRPC server we just started
  37. // This is where the gRPC-Gateway proxies the requests
  38. conn, err := grpc.DialContext(
  39. context.Background(),
  40. "0.0.0.0:8080",
  41. grpc.WithBlock(),
  42. grpc.WithTransportCredentials(insecure.NewCredentials()),
  43. )
  44. if err != nil {
  45. log.Fatalln("Failed to dial server:", err)
  46. }
  47. gwmux := runtime.NewServeMux()
  48. // Register Greeter
  49. err = helloworldpb.RegisterGreeterHandler(context.Background(), gwmux, conn)
  50. if err != nil {
  51. log.Fatalln("Failed to register gateway:", err)
  52. }
  53. gwServer := &http.Server{
  54. Addr: ":8090",
  55. Handler: gwmux,
  56. }
  57. log.Println("Serving gRPC-Gateway on http://0.0.0.0:8090")
  58. log.Fatalln(gwServer.ListenAndServe())
  59. }

For more examples, please refer to our boilerplate repository.

Testing the gRPC-Gateway

Now we can start the server:

  1. $ go run main.go

Then we use cURL to send HTTP requests:

  1. $ curl -X POST -k http://localhost:8090/v1/example/echo -d '{"name": " hello"}'
  1. {"message":"hello world"}

Hopefully, that gives a bit of understanding of how to use the gRPC-Gateway.

Full source code of hello world program can be found here helloworld-grpc-gateway.

Next