GRPC REST Gateway
Dec 29, 2018 11:10 · 697 words · 4 minute read
Наверняка многие слышали о gRPC — открытом RPC-фреймворком от Google, который активно используется Netflix, Kubernetes, Docker и многими другими. Давайте разберемся как можно начать использовать gRPC не отказываясь от уже существующего REST-варианта!
При написании микросервисов использование gRPC позволяет получить несколько преимуществ перед традиционным подходом (REST+JSON) - например, декларативный вариант описания типов и RPC-методов или экономию траффика. Но, к сожалению, часто переход на использование gRPC в существующих проектах затруднителен из-за наличия уже использующихся REST-клиентов, которые невозможно “взять и обновить” за раз.
В gRPC по умолчанию используется Google Protobuf 3
в качестве IDL (Interface Description Language) и HTTP/2 в качестве транспорта. Кодогенерация возможна с помощью 12 (официально) языков программирования, в том числе Go, Java, C++, Python, Ruby, Node.js, C#, PHP. В данной статье в качестве примера мы будем использовать Go. В случае использования gRPC у вас существует только одно место, где вы указываете, как именуются поля, как называются запросы, что они принимают и что возвращают - это .proto
файл.
Для начала необходимо установить protobuf
- сделать это можно несколькими способами, например:
mkdir tmp
cd tmp
git clone https://github.com/google/protobuf
cd protobuf
./autogen.sh
./configure
make
make check
sudo make install
Или (в моем случае установка выполнялась на MacOS) так:
brew install protobuf
Убедиться, что установка прошла успешно можно с помощью команды:
protoc --version
libprotoc 3.6.1
Далее устанавливаем необходимые плагины:
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/golang/protobuf/protoc-gen-go
Убедитесь, что ${GOPATH}/bin
присутствует в вашем ${PATH}
- в моем случае пришлось выполнить дополнительные команды:
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
Структура проекта следующая:
tree
.
├── gateway
│ ├── main.go
│ └── proto
│ └── hello
│ └── hello.proto
└── server
├── main.go
└── proto
└── hello
└── hello.proto
Для сервиса .proto
файл будет выглядеть следующим образом:
syntax = "proto3";
package hello;
// The greeting service definition.
service Say {
// Sends a greeting
rpc Hello(Request) returns (Response) {}
}
// The request message containing the user's name.
message Request {
string name = 1;
}
// The response message containing the greetings
message Response {
string message = 1;
}
Сам сервис выглядит так:
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
hello "./proto/hello"
)
const (
port = ":50051"
)
type server struct{}
func (s *server) Hello(ctx context.Context, in *hello.Request) (*hello.Response, error) {
return &hello.Response{Message: "Hello " + in.Name}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
hello.RegisterSayServer(s, &server{})
reflection.Register(s)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
Генерируем с помощью protoc-компилятора код для сервиса (на языке Go, естественно):
protoc -I/usr/local/include -I. \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
-I server/proto/hello/hello.proto \
--go_out=plugins=grpc:. \
server/proto/hello/hello.proto
Для gRPC-gateway необходимо немного изменить .proto
файл (импортируем google.api и добавляем опции к нашему сервису):
syntax = "proto3";
package hello;
import "google/api/annotations.proto";
// The greeting service definition.
service Say {
// Sends a greeting
rpc Hello(Request) returns (Response) {
option (google.api.http) = {
post: "/greeter/hello"
body: "*"
};
}
}
// The request message containing the user's name.
message Request {
string name = 1;
}
// The response message containing the greetings
message Response {
string message = 1;
}
Код для gRPC-шлюза будет выглядеть следующим образом:
package main
import (
"context"
"flag"
"net/http"
"github.com/golang/glog"
"google.golang.org/grpc"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
hello "./proto/hello"
)
var (
endpoint = flag.String("endpoint", "localhost:50051", "address")
)
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
err := hello.RegisterSayHandlerFromEndpoint(ctx, mux, *endpoint, opts)
if err != nil {
return err
}
return http.ListenAndServe(":80", mux)
}
func main() {
flag.Parse()
defer glog.Flush()
if err := run(); err != nil {
glog.Fatal(err)
}
}
Генерация кода из .proto
файла для gRPC-gateway потребует на одно действие больше:
protoc -I/usr/local/include -I. \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
-I gateway/proto/hello/hello.proto \
--go_out=plugins=grpc:. \
gateway/proto/hello/hello.proto
protoc -I/usr/local/include -I. \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
-I gateway/proto/hello/hello.proto \
--grpc-gateway_out=logtostderr=true:. \
gateway/proto/hello/hello.proto
Приступаем к тестированию, для начала запустим наш сервис:
go run server/main.go
Теперь (в отдельной консоли) запускаем непосредственно gRPC-gateway:
go run gateway/main.go
С помощью утилиты curl
пробуем получить ответ от нашего сервиса:
curl -d '{"name": "John"}' http://localhost/greeter/hello
{"message":"Hello John"}
Все использованные в данном примере файлы можно найти в этом репозитории, а больше информации о gRPC можно найти на официальном сайте.