Introduction
Recently, I have been learning gRPC, and found grpc-gateway, an awesome project, which allows us to call our gRPC service through RESTful JSON API.
It supports languages or clients not well-supported by gRPC to simply maintain the aesthetics and tooling involved with a RESTful architecture. And it's more friendly to front-end developers.
In this article, I will share my rough experience with it.
At first, let's take a look at how grpc-gateway does.
Preparation
Use go get -u to download the following packages.
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/golang/protobuf/protoc-gen-go
Define And Generate
Before we create a gRPC service, we should create a proto file to define what we need, here we create a file named hello.proto to show.
syntax = "proto3";
package protos;
import "google/api/annotations.proto";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post: "/hello_world"
body: "*"
};
}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
As you can see, there is an option for API Configuration, /hello_world is the request URL we defined for HTTP JSON. It means that we can visit SayHello with http://yourdomain.com/hello_world.
Then we should generate a gRPC stub via protoc
protoc -I . --go_out=plugins=grpc:. hello.proto
protoc -I . --grpc-gateway_out=logtostderr=true:. hello.proto
Then we will get the following files.
Implement And Run The Service
Here we just return a string value to the implementation.
package services
import (
"context"
pb "grpc-sample/protos"
"log"
)
type server struct{}
func NewServer() *server {
return &server{}
}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Println("request: ", in.Name)
return &pb.HelloReply{Message: "hello, " + in.Name}, nil
}
Then we will configure this service so that it can run well.
package main
import (
"google.golang.org/grpc"
pb "grpc-sample/protos"
"grpc-sample/services"
"log"
"net"
)
const (
PORT = ":9192"
)
func main() {
lis, err := net.Listen("tcp", PORT)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, services.NewServer())
log.Println("rpc services started, listen on localhost:9192")
s.Serve(lis)
}
Use the following command to run up the service.
go run main.go
After running up, we may get the following result.
HTTP reverse-proxy server
This is the most important step for translation!
What we need to do is to tell something about our gRPC service.
package main
import (
"log"
"net/http"
"github.com/golang/glog"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"golang.org/x/net/context"
"google.golang.org/grpc"
gw "grpc-sample/protos"
)
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
gwmux, err := newGateway(ctx)
if err != nil {
panic(err)
}
mux := http.NewServeMux()
mux.Handle("/", gwmux)
log.Println("grpc-gateway listen on localhost:8080")
return http.ListenAndServe(":8080", mux)
}
func newGateway(ctx context.Context) (http.Handler, error) {
opts := []grpc.DialOption{grpc.WithInsecure()}
gwmux := runtime.NewServeMux()
if err := gw.RegisterGreeterHandlerFromEndpoint(ctx, gwmux, ":9192", opts); err != nil {
return nil, err
}
return gwmux, nil
}
func main() {
defer glog.Flush()
if err := run(); err != nil {
glog.Fatal(err)
}
}
Call Via HTTP Client
Here we use a dotnet core console app to call the reverse proxy server.
class Program
{
static async Task Main(string[] args)
{
using (HttpClient client = new HttpClient())
{
var json = System.Text.Json.JsonSerializer.Serialize(new
{
name = "catcher wong"
});
Console.WriteLine($"request data {json}");
var content = new StringContent(json);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var resp = await client.PostAsync("http://localhost:8080/hello_world", content);
var res = await resp.Content.ReadAsStringAsync();
Console.WriteLine($"response result {res}");
}
Console.ReadLine();
}
}
After running up, we can get the following result.
Summary
grpc-gateway helps us translate a RESTful JSON API into gRPC, this is a good idea. But there are some limitations here because if our gRPC server is written in other languages, it can not support it.