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.

Up Next
    Ebook Download
    View all
    Learn
    View all