GRPC сервис на примере генератора паролей
Как бы выглядел код клиента и сервера для приложения, которое генерирует пароли по маске.
Мы предполагаем, что у вас уже установлен компилятор Go. Вот как его можно установить.
Создадим каталог для проекта и перейдем в него:
mkdir hello && cd hello
Инициализируем этот каталог как модуль для Go
go mod init netangels/passwordservice
Установим GRPC
go get -u google.golang.org/grpc
Установим компилятор protoc
go get -u github.com/golang/protobuf/protoc-gen-go
Создадим каталоги client
, server
, proto
mkdir client server proto
1. Protocol Buffers
Protocol Buffers — протокол сериализации (передачи) структурированных данных, предложенный Google как бинарная альтернатива текстовому формату XML. Подробнее на wikipedia.org.
Когда вы пишите gRPC приложение, то обычно создается *.proto файл и компилируется. А затем уже начинается разработка приложения.
Заполним описание протобафа:
/* версия протобаф файла. 2я версия уже устарела */
syntax = "proto3";
/* весь полученный из этого протобафа код добавляем
в пакет passwordservice */
package passwordservice;
message PasswordRequest {
string sample = 1;
}
message PasswordResponse {
string password = 1;
}
/* сервис PasswordGeneratorService с методом Generate, который
получает PasswordRequest, а возращает PasswordResponse */
service PasswordGeneratorService {
rpc Generate(PasswordRequest) returns (PasswordResponse) {}
}
Сохраним это в каталоге proto
как файл passwordservice.protoc
. Теперь его нужно скомпилировать. Компиляция файла означает создание кода для выбранного языка. Мы создадим код для Go, опция называется “–go_out”. Для других языков опции будут называться по-другому.
Компилируем:
protoc -I proto proto/passwordservice.proto --go_out=plugins=grpc:proto/
Вывод будет примерно таким:
protoc -I proto proto/passwordservice.proto --go_out=plugins=grpc:proto/
2020/04/17 13:11:11 WARNING: Missing 'go_package' option in "passwordservice.proto", please specify:
option go_package = ".;passwordservice";
A future release of protoc-gen-go will require this be specified.
See https://developers.google.com/protocol-buffers/docs/reference/go-generated#package for more information.
Дело осталось за малым - написать бизнес логику 😀
2. Серверная часть
Теперь заполним описание сервера на Go. Возьмем готовую библиотеку из Golang, которая генерирует пароли.
func (s *PasswordGeneratorServiceServer) Generate(ctx context.Context,
req *ps.PasswordRequest) (*ps.PasswordResponse, error) {
var err error response := new(ps.PasswordResponse) requirements := garbler.MakeRequirements(req.Sample) response.Password, err = garbler.NewPassword(&requirements)
return response, err}
Для реализации TCP сервера мы будем использовать стандартные для Golang методы. Если их опустить, то получается что нужно написать вообще одну строку кода, она выделена ниже:
server := grpc.NewServer()
instance := new(PasswordGeneratorServiceServer)
ps.RegisterPasswordGeneratorServiceServer(server, instance)
listener, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal("Unable to create grpc listener:", err)
}
if err = server.Serve(listener); err != nil {
log.Fatal("Unable to start server:", err)
}
Сохраним весь код серверной части в каталоге server
как файл main.go
package main
import (
"context"
"log"
"net"
garbler "github.com/michaelbironneau/garbler/lib"
"google.golang.org/grpc"
ps "netangels/passwordservice/proto"
)
type PasswordGeneratorServiceServer struct {
}
func (s *PasswordGeneratorServiceServer) Generate(ctx context.Context,
req *ps.PasswordRequest) (*ps.PasswordResponse, error) {
var err error
response := new(ps.PasswordResponse)
requirements := garbler.MakeRequirements(req.Sample)
response.Password, err = garbler.NewPassword(&requirements)
return response, err
}
func main() {
server := grpc.NewServer()
instance := new(PasswordGeneratorServiceServer)
ps.RegisterPasswordGeneratorServiceServer(server, instance)
listener, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal("Unable to create grpc listener:", err)
}
if err = server.Serve(listener); err != nil {
log.Fatal("Unable to start server:", err)
}
}
3. Клиентская часть
Напишем клиентскую часть
package main
import (
"context"
"log"
"os"
ps "netangels/passwordservice/proto"
"google.golang.org/grpc"
)
func main() {
conn, _ := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
client := ps.NewPasswordGeneratorServiceClient(conn)
sample := os.Args[1]
resp, err := client.Generate(context.Background(),
&ps.PasswordRequest{Sample: sample})
if err != nil {
log.Fatalf("could not get answer: %v", err)
}
log.Println("New password:", resp.Password)
}
4. Запуск проекта
У нас получится такая файловая структура:
.
├── client
│ └── main.go
├── go.mod
├── go.sum
├── proto
│ ├── passwordservice.pb.go
│ └── passwordservice.proto
└── server
└── main.go
Скомпилируем сервер:
go build -race -ldflags "-s -w" -o bin/server server/main.go
Теперь запустим его:
bin/server
Не закрывая окно с запущенным сервером в соседнем терминале скомпилируем клиент:
go build -race -ldflags "-s -w" -o bin/client client/main.go
Теперь запустим его и передадим “gimme.a.pass” в качестве аргумента:
bin/client "gimme.a.pass"
Все получилось, если результатом будет что-то вроде:
2020/04/17 13:18:17 New password: "dipmolejma*
5. Дополнительно. Добавить вызовы в стиле REST
GRPC — бинарный протокол, поэтому сразу работать с ним как с REST через curl или wget не получится. А без этого становится существенно сложнее отлаживать проект.
Но есть расширение, которое называется Rest GRPC Gateway. Он работает как прокси сервер: