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 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 configurate 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.
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.
- 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 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.