Using gRPC-gateway To Call gRPC Service Via RESTful JSON API

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:
  1. go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway  
  2. 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.
  1. syntax = "proto3";  
  2.   
  3. package protos;  
  4.   
  5. import "google/api/annotations.proto";  
  6.   
  7. service Greeter {  
  8.   rpc SayHello (HelloRequest) returns (HelloReply) {  
  9.       option (google.api.http) = {  
  10.                 post: "/hello_world"  
  11.                 body: "*"  
  12.             };  
  13.   }  
  14. }  
  15.    
  16. message HelloRequest {  
  17.   string name = 1;  
  18. }  
  19.    
  20. message HelloReply {  
  21.   string message = 1;  
  22. }  
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 gRPC stub via protoc 
  1. protoc -I . --go_out=plugins=grpc:. hello.proto  
  2. 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.
  1. package services  
  2.   
  3. import (  
  4.     "context"  
  5.     pb "grpc-sample/protos"  
  6.     "log"  
  7. )  
  8.   
  9. type server struct{}  
  10.   
  11. func NewServer() *server {  
  12.     return &server{}  
  13. }  
  14.   
  15. func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {  
  16.     log.Println("request: ", in.Name)  
  17.     return &pb.HelloReply{Message: "hello, " + in.Name}, nil  
  18. }  
Then we will configurate this service so that it can run well. 
  1. package main  
  2.   
  3. import (  
  4.     "google.golang.org/grpc"  
  5.     pb "grpc-sample/protos"  
  6.     "grpc-sample/services"  
  7.     "log"  
  8.     "net"  
  9. )  
  10.   
  11. const (  
  12.     PORT = ":9192"  
  13. )  
  14.   
  15. func main() {  
  16.     lis, err := net.Listen("tcp", PORT)  
  17.   
  18.     if err != nil {  
  19.         log.Fatalf("failed to listen: %v", err)  
  20.     }  
  21.   
  22.     s := grpc.NewServer()  
  23.     pb.RegisterGreeterServer(s, services.NewServer())  
  24.     log.Println("rpc services started, listen on localhost:9192")  
  25.     s.Serve(lis)  
  26. }  
Use the following command to run up the service.
  1. 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 of our gRPC service.
  1. package main  
  2.   
  3. import (  
  4.     "log"  
  5.     "net/http"  
  6.   
  7.     "github.com/golang/glog"  
  8.     "github.com/grpc-ecosystem/grpc-gateway/runtime"  
  9.     "golang.org/x/net/context"  
  10.     "google.golang.org/grpc"  
  11.     gw "grpc-sample/protos"  
  12. )  
  13.   
  14. func run() error {  
  15.     ctx := context.Background()  
  16.     ctx, cancel := context.WithCancel(ctx)  
  17.     defer cancel()  
  18.   
  19.     gwmux, err := newGateway(ctx)  
  20.     if err != nil {  
  21.         panic(err)  
  22.     }  
  23.   
  24.     mux := http.NewServeMux()  
  25.     mux.Handle("/", gwmux)  
  26.   
  27.     log.Println("grpc-gateway listen on localhost:8080")  
  28.     return http.ListenAndServe(":8080", mux)  
  29. }  
  30.   
  31. func newGateway(ctx context.Context) (http.Handler, error) {  
  32.   
  33.     opts := []grpc.DialOption{grpc.WithInsecure()}  
  34.   
  35.     gwmux := runtime.NewServeMux()  
  36.     if err := gw.RegisterGreeterHandlerFromEndpoint(ctx, gwmux, ":9192", opts); err != nil {  
  37.         return nil, err  
  38.     }  
  39.   
  40.     return gwmux, nil  
  41. }  
  42.   
  43. func main() {  
  44.     defer glog.Flush()  
  45.   
  46.     if err := run(); err != nil {  
  47.         glog.Fatal(err)  
  48.     }  
  49. }  

Call Via HTTP Client

Here we use a dotnet core console app to call the reverse-proxy server.
  1. class Program  
  2. {  
  3.     static async Task Main(string[] args)  
  4.     {  
  5.         using (HttpClient client = new HttpClient())  
  6.         {  
  7.             var json = System.Text.Json.JsonSerializer.Serialize(new  
  8.             {  
  9.                 name = "catcher wong"  
  10.             });  
  11.               
  12.             Console.WriteLine($"request data {json}");  
  13.               
  14.             var content = new StringContent(json);  
  15.             content.Headers.ContentType = new MediaTypeHeaderValue("application/json");  
  16.   
  17.             var resp = await client.PostAsync("http://localhost:8080/hello_world", content);  
  18.   
  19.             var res = await resp.Content.ReadAsStringAsync();  
  20.   
  21.             Console.WriteLine($"response result {res}");  
  22.         }  
  23.   
  24.         Console.ReadLine();  
  25.     }  
  26. }  
After running up, we can get the following result.
 
 
 

Summary

 
grpc-gateway helps us translates 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.