Сервер на Golang с gRPC и REST API одновременно
В этой статье описывается простой способ создания Go-сервера, который одновременно прослушивает gRPC и REST-запросы.
Самое интересное, что мы будем использовать простую схему Protocol Buffer, которая будет компилироваться для обоих типов API.
TL;DR – Вы можете посмотреть, как это делается в моем репозитории Github.
Начнем с инициализации нового проекта Go.
$ mkdir hello
$ cd hello
$ go mod init github.com/your-username/hello
Создание буферов протоколов
Установить компилятор буферов протоколов
Если у вас еще не установлен protoc, следуйте этому руководству по установке.
Затем добавьте поддержку protoc для grpc-gateway и, опционально, для open-api-v2:
$ go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
$ go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest
Далее необходимо убедиться, что необходимые зависимости доступны компилятору во время компиляции. Это можно сделать, вручную клонировав и скопировав соответствующие файлы из репозитория googleapis в свой проект в папку ./google/api. Потребуются следующие файлы:
google/api/annotations.proto
google/api/field_behavior.proto
google/api/http.proto
google/api/httpbody.proto
Создание файла proto
Теперь создадим буферы протокола. Создадим файл ./proto/hello.proto и заполним его следующим кодом:
syntax = "proto3";
package hello;
option go_package = "github.com/your-username/hello";
import "google/api/annotations.proto";
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
info: {
title: "HelloService";
version: "1.0";
contact: {
name: "grpc-with-rest";
url: "https://github.com/your-username/hello";
};
};
schemes: HTTP;
consumes: "application/json";
produces: "application/json";
responses: {
key: "404";
value: {
description: "Returned when the resource does not exist.";
schema: {
json_schema: {
type: STRING;
}
}
}
}
};
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse) {
option(google.api.http) = {
get: "/api/hello/{name}",
};
}
}
Компиляция прототипов
И, наконец, запустим в консоли скрипт protoc, который скомпилирует буферы протокола в необходимый код:
$ protoc -I ./proto \
--go_out ./proto --go_opt paths=source_relative \
--go-grpc_out ./proto --go-grpc_opt paths=source_relative \
--grpc-gateway_out ./proto --grpc-gateway_opt paths=source_relative \
--openapiv2_out ./proto --openapiv2_opt use_go_templates=true \
./proto/hello.proto
Это должно создать 4 новых файла в папке ./proto.
Создание сервера
Создайте файл ./server/main.go и заполните его этим кодом:
package main
import (
"context"
"log"
"net"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/reflection"
pb "github.com/jannden/golang-examples/grpc-with-rest/proto"
)
type server struct {
pb.UnimplementedHelloServiceServer
}
func NewServer() *server {
return &server{}
}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
return &pb.HelloResponse{Message: "Hello, " + in.Name}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalln("Failed to listen:", err)
}
s := grpc.NewServer()
reflection.Register(s)
pb.RegisterHelloServiceServer(s, &server{})
go func() {
log.Println("Serving gRPC on 0.0.0.0:50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("Failed to serve gRPC server: %v", err)
}
}()
conn, err := grpc.DialContext(
context.Background(),
"0.0.0.0:50051",
grpc.WithBlock(),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
log.Fatalln("Failed to dial server:", err)
}
gwmux := runtime.NewServeMux()
err = pb.RegisterHelloServiceHandler(context.Background(), gwmux, conn)
if err != nil {
log.Fatalln("Failed to register gateway:", err)
}
gwServer := &http.Server{
Addr: ":50052",
Handler: gwmux,
}
log.Println("Serving gRPC-Gateway for REST on http://0.0.0.0:50052")
if err := gwServer.ListenAndServe(); err != nil {
log.Fatalf("Failed to serve gRPC-Gateway server: %v", err)
}
}
Запуск сервера
Запустите сервер:
$ go run server/main.go
Откройте новый терминал.
Протестируйте соединение с gRPC с помощью grpcurl:
$ grpcurl -d '{"name":"John"}' -plaintext 0.0.0.0:50051 hello.HelloService/SayHello
Протестируйте соединение с REST с помощью curl:
$ curl 0.0.0.0:50052/api/hello/John
Заключение
Надеюсь, вы хорошо разобрались в создании Go-сервера с gRPC и REST 😊.