In this article, we will learn how to create a simple grpc-gateway with Go. For this, we need to prepare our Go project for the grpc-gateway. You can find detailed information on this subject in the following sections of the article. Now let’s briefly look at what grpc-gateway is.

The gRPC-Gateway is a plugin of the Google protocol buffers compiler protoc. It reads protobuf service definitions and generates a reverse-proxy server which translates a RESTful HTTP API into gRPC. This server is generated according to the google.api.http annotations in your service definitions. gRPC does great work when it comes to speed and ease of use. Indeed with generated client and server code, it all comes to as little as implementing the interfaces to have a solution ready to run. There are plenty of cases however when you still need to provide clients with REST endpoints. With, you can generate a reverse proxy that would translate REST into gRPC call via marshalling of the JSON request body into respective Go structures followed by the RPC endpoint call. Let’s start writing code for gRPC-gateway with Go.

Prerequisites

Before we start coding create a directory on your local machine called grpc-gateway for your project from the terminal.

$ mkdir grpc-gateway

You can open the folder by running the following command with Visual Studio Code.

$ cd grpc-gateway

Create main.go file and initialize your local project by running the following Go command.

$ go mod init github.com/favtuts/grpc-gateway

We have to install some tools. Use go install to download the following packages. These dependencies will be recorded in the go.mod file which was created by the previous command. This installs the protoc generator plugins we need to generate the stubs.

$ go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

Make sure to add $GOPATH/bin to your $PATH so that executables installed via go install are available on your $PATH . If you haven’t done this before you can do it with the command below.

export PATH=$PATH:$GOPATH/bin

Create Protobuf Model

Inside protobuf, it defines the services and messages that are available. For a project with structure, create proto/hello.proto by running the following commands with git bash.

$ mkdir proto 
$ touch proto/hello.proto

When you run the commands your project folder will look like below.

proto  
    └── hello.proto

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/message to our SayHello RPC.

proto/hello.proto

syntax = "proto3";

option go_package = "github.com/favtuts/grpc-gateway"; 

import "google/api/annotations.proto";

// Here is the overall greeting service definition where we define all our endpoints
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloResponse) {
    option (google.api.http) = {
      post: "/v1/message"
      body: "*"
    };
  }
}

// The request message containing the user's name
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloResponse {
  string message = 1;
}
  • (8)service keyword defines a service, and rpc defines the functionality of the service. In this example, our Greeter provides a SayHello a function that requires input HelloRequest message and output a HelloResponse message.
  • (11) Adds the custom google.api.http HTTP to gRPC transcoding details.
  • (12) Declares that this gRPC method transcodes to the HTTP POST method and the path /v1/message.
  • (13) Declares the body schema of the HTTP request.

gRPC Gateway requires a few proto files hosted within the googleapis repository. Unfortunately, we have to manually add these proto files to our project. Copy a subset of googleapis from the official repository to your local proto file structure. Then your file structure should look like this:

proto
├── google
   └── api
       ├── annotations.proto
       └── http.proto
└── hello.proto

We use the go ,go-grpc and go-gatewayplugins to generate Go types and gRPC service definitions. We’re outputting the generated files relative to the proto folder, and we’re using the paths=source_relative option, which means that the generated files will appear in the same directory as the source .proto file.

$ protoc -I ./proto \
  --go_out ./proto --go_opt paths=source_relative \
  --go-grpc_out ./proto --go-grpc_opt paths=source_relative \
  --grpc-gateway_out ./proto --grpc-gateway_opt paths=source_relative \
  ./proto/hello.proto

This will have generated a *.pb.go , *_grpc.pb.go and a *.gw.pb.go file for proto/hello.proto.

Create main.go

Add code to the main.go file to create the gateway entry point. For this, run the commands below.

$ mkdir cmd
$ touch cmd/main.go

cmd/main.go

package main

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

	"github.com/favtuts/grpc-gateway/config"
	pb "github.com/favtuts/grpc-gateway/proto"
	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

type server struct {
	pb.UnimplementedGreeterServer
}

func (s *server) SayHello(ctx context.Context, request *pb.HelloRequest) (*pb.HelloResponse, error) {
	name := request.Name
	response := &pb.HelloResponse{
		Message: "Hello " + name,
	}
	return response, nil
}

func main() {
	// Create a listener on TCP port
	lis, err := net.Listen(config.DefaultGRPCServerConfig.Network, config.DefaultGRPCServerConfig.Address)
	if err != nil {
		log.Fatalln("Failed to listen:", err)
	}

	// Create a gRPC server object
	s := grpc.NewServer()
	// Attach the Greeter service to the server
	pb.RegisterGreeterServer(s, &server{})
	// Serve gRPC server
	log.Println("Serving gRPC on connection ")
	go func() {
		log.Fatalln(s.Serve(lis))
	}()

	// Create a client connection to the gRPC server we just started
	conn, err := grpc.Dial(config.DefaultGRPCServerConfig.Address, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalln("Failed to dial server:", err)
	}
	defer conn.Close()

	mux := runtime.NewServeMux()
	// Register Greeter
	err = pb.RegisterGreeterHandler(context.Background(), mux, conn)
	if err != nil {
		log.Fatalln("Failed to register gateway:", err)
	}

	gwServer := &http.Server{
		Addr:    config.DefaultReverseConfig.Address,
		Handler: mux,
	}

	log.Println("Serving gRPC-Gateway on connection")
	log.Fatalln(gwServer.ListenAndServe())
}
  • (19) The code generated in the hello.pb.go . To implement the server, the struct type needs to implement the function inside the interface. Then in the implementation, it takes in the name from the request and returns a string Hello + name.
  • (49) RegisterGreeterHandler registers the HTTP handlers for service Greeter to “mux”. The handlers forward request to the grpc endpoint over “conn”.

Run the gateway example

Now we can start the server:

$ go run cmd/main.go

Then we use cURL to send HTTP requests:

curl --location --request POST 'http://localhost:5000/v1/message' \
--header 'Content-Type: application/json' \
--data-raw '{"name": "everyone"}'
                        .  .  .
{"message":"Hello everyone"}

In this article, I tried to show what is grpc-gateway and how to create a simple grpc gateway with Go. I hope it was useful for you.

The complete code for the article is available in this GitHub repo

Thank you for reading.

Leave a Reply

Your email address will not be published. Required fields are marked *