Golang grpc-gatewayでRESTful APIを作成する

#はじめに
**「gRPCを使ってみたものの、REST-APIは使えないのか？」**
**「gRPCに加えて、REST-APIを使いたい」**
**「gRPCで作ったものを簡単に検証したい」**

と思って調べると、`grpc-gateway`というものがありましたので、使ってみます。
https://github.com/grpc-ecosystem/grpc-gateway

#gRPC
gRPCについては、別途記事を書いていますので、こちらを参照してください。
https://qiita.com/ryu3/items/d5cc28a412a06e17ef98

#grpc-gatewayの概要

grpc-gatewayはgRPCで書かれたAPIを、JSON over HTTPのAPIに変換してくれます。コード生成器として機能し、ある種のリバースプロキシサーバーを生成します。下の図をご覧ください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317035/1efa8277-e25c-8a68-7956-e9460bd4626c.png)

#インストール

下記コマンドでインストールしてください。

```
$ go get -u -v github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
$ go get -u -v github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
$ go get -u -v github.com/golang/protobuf/protoc-gen-go
```

#使ってみよう
##1.gRPCサービスを定義

```proto/service.proto
syntax = "proto3";
package example;

import "google/api/annotations.proto";

service HelloWorldService {
  rpc SayHello (HelloRequest) returns (HelloReply) {
  }

  rpc GetUser(GetUserRequest) returns (User) {
  }

  rpc CreateUser(CreateUserRequest) returns (User) {
  }
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

message GetUserRequest {
  string id = 1;
}

message CreateUserRequest {
  string name = 1;
}

message User {
  string id = 1;
  string name = 2;
}
```

##2.`google.api.http`を追加します。

```service.proto
syntax = "proto3";
package example;

import "google/api/annotations.proto";

service HelloWorldService {
  rpc SayHello (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
      get: "/v1/example/sayhello/{name}"
    };
  }

  rpc GetUser(GetUserRequest) returns (User) {
    option (google.api.http) = {
        get: "/v1/example/users/{id}"
    };
  }

  rpc CreateUser(CreateUserRequest) returns (User) {
      option (google.api.http) = {
          post: "/v1/example/users"
          body: "*"
      };
  }
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

message GetUserRequest {
  string id = 1;
}

message CreateUserRequest {
  string name = 1;
}

message User {
  string id = 1;
  string name = 2;
}

```
##3.gRPC stub を生成する

`protoc`コンパイラを使って、`.proto`ファイルをビルドして、`.pb.go`ファイルを作成します

```
protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --go_out=plugins=grpc:. \
  service.proto
```

`path/to/your_service.pb.go`が生成されます。

##4.`reverse-proxy`を生成する

`protoc`コンパイラを使って、`.proto`ファイルをビルドして、`.pb.gw.go`ファイルを作成します

```
protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --grpc-gateway_out=logtostderr=true:. \
  service.proto
```

##5. Gateway / サーバの作成

gateway / サーバのを下記のように作成します。

```greeter_server/main.go
package main

import (
	"context"
	"log"
	"net"

	"google.golang.org/grpc"
	pb "../proto"
)

const (
	port = ":5001"
)

// server is used to implement HelloWorldServer.
type server struct{}

// SayHello implements HelloWorldServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	log.Printf("Received: %v", in.Name)
	return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

// GetUser
func (s *server) GetUser(ctx context.Context, in *pb.GetUserRequest) (*pb.User, error) {
	log.Printf("Received: %v", in.Id)
	return &pb.User{
		Id: in.Id,
		Name: "SampleUser"}, nil
}

// CreateUser
func (s *server) CreateUser(ctx context.Context, in *pb.CreateUserRequest) (*pb.User, error) {
	log.Printf("Received: %v", in.Name)
	return &pb.User{
		Id: "123",
		Name: in.Name}, nil
}

func main() {
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterHelloWorldServiceServer(s, &server{})
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

```

```greeter_gateway/main.go
package main

import (
  "flag"
  "fmt"
  "net/http"

  "github.com/golang/glog"
  "golang.org/x/net/context"
  "github.com/grpc-ecosystem/grpc-gateway/runtime"
  "google.golang.org/grpc"

  gw "../proto"
)

func run() error {
  ctx := context.Background()
  ctx, cancel := context.WithCancel(ctx)
  defer cancel()

  mux := runtime.NewServeMux()
  opts := []grpc.DialOption{grpc.WithInsecure()}
  endpoint := fmt.Sprintf("localhost:5001")
  err := gw.RegisterHelloWorldServiceHandlerFromEndpoint(ctx, mux, endpoint, opts)
  if err != nil {
    return err
  }

  return http.ListenAndServe(":5000", mux)
}

func main() {
  flag.Parse()
  defer glog.Flush()

  if err := run(); err != nil {
    glog.Fatal(err)
  }
}
```

##動作確認

gatewayとサーバを実行してみましょう。

```
go run greeter_gateway/main.go
```

別のターミナルを開いて、下記を実行します。

```
go run greeter_server/main.go
```

`curl`コマンドでREST APIを実行してみましょう。
期待通りにデータを取得できています。

ユーザ名(nakata)を入れて、`sayhello`へGETします。

```
curl -X GET http://localhost:5000/v1/example/sayhello/nakata

{"message":"Hello nakata"}
```

ユーザID(10)を入れて、`users`へGETします。

```
curl -X GET http://localhost:5000/v1/example/users/10

{"id":"10","name":"SampleUser"}
```

ユーザ名(nakata)を入れて、`users`へPOSTします。

```
curl -X POST http://localhost:5000/v1/example/users -d '{"name":"nakata"}'

{"id":"123","name":"nakata"}
```

#おわりに
gRPCに対応するRESTful APIを自動生成しました。
gRPCサーバの実装をcurlコマンドで確認することができました。