GRPC сервис на примере генератора паролей

17 апреля 2020
1557
Linux
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. Он работает как прокси сервер:

Подробнее о подключении Rest GRPC Gateway.

Рекомендуемые статьи:

Мы используем файлы cookie для предоставления наших услуг, а также для аналитики и маркетинга. Продолжая просматривать наш веб-сайт, вы соглашаетесь на использование нами файлов cookie.
ОК