LoginSignup
43
35

More than 5 years have passed since last update.

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

Posted at

はじめに

「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

インストール

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

$ 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コマンドで確認することができました。

43
35
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
43
35