Всем привет!
Данная статья является гайдом по построению REST прокси поверх существующих gRPC сервисов. После прочтения данного материала можно будет вызывать любой из существующих gRPC сервисов используя стандартный REST API, а так же получить полную документацию в swagger формате.
Для полного понимания данного материала необходимо:
-
Уметь писать на go
-
Понимать основные принципы gRPC (proto, кодогенерация, типы …)
Мотивация для создания REST шлюза поверх существующего gRPC API может быть разная (альтернативный доступ, удобство тестирования и др.), однако как показывает практика gRPC не всегда хватает.
Дополнить существующий go gRPC сервис можно довольно быстро (от ~10 минут до суток) в зависимости от сложности существующего API, и степени проработанности REST прокси, однако в базовом формате это можно сделать относительно (сравнивая с ручным написанием сериализаторов и сервиса `перевызывающего gRPC`) быстро.
Для нетерпеливых есть репозиторий, в котором есть все необходимые материалы для подключения прокси. Далее идет пошаговый гайд как добавить прокси к существующему сервису.
1 — Устанавливаем необходимые инструменты
В данном блоке указаны пакеты которые необходимо установить (используя go) для генерации gateway кода в дополнение к коду gRPC
// +build tools package tools import ( _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway" _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2" _ "google.golang.org/grpc/cmd/protoc-gen-go-grpc" _ "google.golang.org/protobuf/cmd/protoc-gen-go" )
go get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway go get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 go get google.golang.org/protobuf/cmd/protoc-gen-go go get google.golang.org/grpc/cmd/protoc-gen-go-grpc
В случае проблем с установкой можно поискать решение здесь.
2 — Опционально (для swagger) — добавляем rest опции в proto файл
Важный нюанс — google/api/annotaitons.proto — это файл который не отгружается по умолчанию вметсе с инструментами для генерации прокси, его нужно скачать отдельно и положить в рабочую директорию с проектом с исходным proto файлом.
В репозитории есть необходмые файлы и их можно взять там. Для генерации документации и присвоения кастомных путей нужны следующие файлы:

Вот ссылки:
Далее если надо добавить опции в proto файл в следующем формате:
syntax = "proto3"; package pb; option go_package = "/pb"; import "google/api/annotations.proto"; service Gateway { rpc PostExample(Message) returns (Message) { option (google.api.http) = { post: "/post" body: "*" }; } rpc GetExample(Message) returns (Message) { option (google.api.http) = { get: "/get/{id}" }; } rpc DeleteExample(Message) returns (Message) { option (google.api.http) = { delete: "/delete/{id}" }; } rpc PutExample(Message) returns (Message) { option (google.api.http) = { put: "/put" body: "*" }; } rpc PatchExample(Message) returns (Message) { option (google.api.http) = { patch: "/patch" body: "*" }; } } message Message { uint64 id = 1; }
К существующим rpc методам мы добавили опции для получения `пути` в ссылках.
3 — Генерируем новый код с учетом rest proxy
Вот один из примеров:
protoc --go_out=. --go-grpc_out=. --grpc-gateway_out=. --grpc-gateway_opt generate_unbound_methods=true --openapiv2_out . api.prorototo
Другие примеры генерации кода можно найти тут.
4 — Пишем функцию которая запустит прокси сервер
Данная функция запускает rest сервер который будет обращаться к gRPC серверу и использовать его для обработки сообщений:
func runRest() { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() mux := runtime.NewServeMux() opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} err := pb.RegisterGatewayHandlerFromEndpoint(ctx, mux, "localhost:12201", opts) if err != nil { panic(err) } log.Printf("server listening at 8081") if err := http.ListenAndServe(":8081", mux); err != nil { panic(err) } }
5 — Запускаем сервер в отдельной горутине и проверяем что всё работает
Необходимо добавить:
func main() { go runRest() runGrpc() }

6 — Опционально — проверяем документацию в swagger
Берем сгенерированный файл api.swagger.json и вставляем в swagger editor.

В качестве результата мы должны получить хорошую документацию в формате openapi.
Заключение
Таким образом мы получили дополнительный способ доступа к нашему gRPC сервиса малой кровью (проще чем писать собственный код и сериализаторы), что может быть полезно в ряде случаев.
Использованный инструмент: grpc-gateway
Репозиторий с примером: gateway
Полный код сервера на go:
package main import ( "context" "fmt" "log" "net" "net/http" "gateway/pb" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) type server struct { pb.UnimplementedGatewayServer } func (s *server) PostExample(ctx context.Context, in *pb.Message) (*pb.Message, error) { fmt.Println(in) return &pb.Message{Id: in.Id}, nil } func (s *server) GetExample(ctx context.Context, in *pb.Message) (*pb.Message, error) { fmt.Println(in) return &pb.Message{Id: in.Id}, nil } func (s *server) DeleteExample(ctx context.Context, in *pb.Message) (*pb.Message, error) { fmt.Println(in) return &pb.Message{Id: in.Id}, nil } func (s *server) PutExample(ctx context.Context, in *pb.Message) (*pb.Message, error) { fmt.Println(in) return &pb.Message{Id: in.Id}, nil } func (s *server) PatchExample(ctx context.Context, in *pb.Message) (*pb.Message, error) { fmt.Println(in) return &pb.Message{Id: in.Id}, nil } func runRest() { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() mux := runtime.NewServeMux() opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} err := pb.RegisterGatewayHandlerFromEndpoint(ctx, mux, "localhost:12201", opts) if err != nil { panic(err) } log.Printf("server listening at 8081") if err := http.ListenAndServe(":8081", mux); err != nil { panic(err) } } func runGrpc() { lis, err := net.Listen("tcp", ":12201") if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGatewayServer(s, &server{}) log.Printf("server listening at %v", lis.Addr()) if err := s.Serve(lis); err != nil { panic(err) } } func main() { go runRest() runGrpc() }
ссылка на оригинал статьи https://habr.com/ru/post/658769/
Добавить комментарий