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.

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.

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.

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.

Output

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.


Similar Articles