GRPC REST Gateway

Dec 29, 2018 11:10 · 697 words · 4 minute read gRPC curl

Наверняка многие слышали о 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 можно найти на официальном сайте.

tweet Share