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, andrpc
defines the functionality of the service. In this example, ourGreeter
provides aSayHello
a function that requires inputHelloRequest
message and output aHelloResponse
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-gateway
plugins 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 stringHello + 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