С момента появления gRPC он стал очень популярным в сообществе разработчиков API. Причина, по которой gRPC стал настолько популярным, заключалась в его поддержке реализации многоязычного интерфейса (сервер и клиент могут быть написаны на разных языках), его ориентации на производительность с самого начала (gRPC основан на HTTP / 2) и отличном наборе инструментов (использование gRPC protobuf для сообщений и описаний сервисов, а клиенты могут быть сгенерированы автоматически без написания единой строчки кода).

Еще несколько лет назад это совершенство было ограничено мобильными телефонами и серверами и было недоступно для фронтенд-разработчиков, и им по-прежнему приходилось использовать интерфейсы REST. Это произошло из-за ограничений HTTP / 2 в браузерах. Например

  1. Запрос в браузере не может быть принудительно HTTP / 2.
  2. Фреймы HTTP / 2 не доступны библиотекам напрямую.

На очереди grpc-web, попытка Google и Improbable реализовать спецификацию gRPC для браузеров. В этой спецификации используется HTTP / 1.x с прокси-сервером шлюза, например Envoy (который прозрачно переводит HTTP / 1.x в HTTP / 2, ожидаемый сервером gRPC).

gRPC-web поддерживает только унарную и серверную потоковую передачу в режиме grpcwebtext (мы расскажем, что это означает, в следующих разделах)

В этом посте мы реализуем сервер gRPC в golang и реализуем клиента на JavaScript с использованием gRPC-web. Мы увидим примеры как унарных, так и серверных вызовов API потоковой передачи.

Итак, приступим.

Таблица содержания

  1. Предпосылки
  2. Определения Protobuf
  3. Реализация API унарного сервера
  4. Использование унарного API
  5. Исправление ошибок связи с Envoy
  6. Реализация потоковой передачи на стороне сервера
  7. Заключение

Предпосылки

Нам нужно установить несколько инструментов, чтобы успешно проработать этот пост.

  1. VS Code или редактор кода по вашему выбору, чтобы написать наши коды.
  2. Компилятор и инструменты Golang.
  3. Python3 - мы будем использовать HTTP-сервер Python для обслуживания нашего клиента.
  4. Инструменты Node (npm поставляется в комплекте с node) и npx.
  5. Докер - мы будем запускать прокси-сервер Envoy в качестве контейнера докеров.
  6. protoc компилятор protobuf.
  7. Golang пакет gRPC и плагин protobuf protoc.
  8. protoc-gen-grpc-webplugin от gRPC-web.

Следуйте инструкциям по установке для каждого отдельного инструмента. Я пропускаю приведенные здесь инструкции для краткости, но дайте мне знать, если вы столкнетесь с какими-либо ошибками, я буду рад помочь.

Этот пост ни в коем случае не является вводным для любого из используемых здесь языков или фреймворков. Я свяжу вводный материал для каждого из них, чтобы вы быстрее освоились.

Определения Protobuf

Мы начнем с создания определения protobuf для нашей службы. Создайте calculator.proto файл и поместите его в папку protos. На данный момент мы просто добавим определение унарной службы и доработаем его, добавив потоковую передачу на стороне сервера во второй части.

grpc-web
    ├── protos
    │   └── calculator.proto
    ├── server
    └── client

Мы добавили два сообщения, а именно AddRequest, которое требует сложения двух чисел, AddResponse, которое вернуло результат сложения, и службу калькулятора с одним вызовом rpc Add, который принимает AddRequest и возвращает AddResponse.

Реализация API унарного сервера

Мы скомпилируем calculator.proto файл в Go с помощью плагина protoc and go protobuf.

Создайте папку calculatorpb внутри папки сервера и выполните следующую команду.

protoc calculator.proto --go_out=plugins=grpc:../server/calculatorpb/

Это сгенерирует calculator.pb.go файл, и ваша структура каталогов будет выглядеть следующим образом.

grpc-web
    ├── protos
    │   └── calculator.proto
    ├── server
    │   └── calculatorpb
    │       └── calculator.pb.go
    └── client

Теперь инициализируйте модуль go, выполнив следующую команду в папке server.

go mod init github.com/kaysush/grpc-calculator

Это добавит go.mod файл в вашу server папку. Создание модуля из нашей server папки гарантирует, что мы сможем получить доступ к пакету в calculator.pb.go файле в нашем server.go файле, который мы добавим на следующем шаге.

Теперь давайте реализуем сервер.

grpc-web
    ├── protos
    │   └── calculator.proto
    ├── server
    │   ├── calculatorpb
    │   │   └── calculator.pb.go
    │   ├── go.mod
    │   └── server.go
    └── client

Запустите server.go

go run server.go

Вы должны увидеть сообщение Starting Calculator server на экране.

Теперь сервер gRPC готов обслуживать запросы на порт 50551.

Использование унарного API

Теперь, когда наш сервер запущен, давайте реализуем нашего клиента, используя gRPC-web.

Скомпилируйте calculator.proto файл для JavaScript, используя proto-gen-grpc-web плагин.

protoc calculator.proto --js_out=import_style=commonjs,binary:../client --grpc-web_out=import_style=commonjs,mode=grpcwebtext:../client

Это сгенерирует calculator_pb.js (с определениями сообщений) и calculator_grpc_web_pb.js (с нашей клиентской реализацией).

grpc-web
    ├── protos
    │   └── calculator.proto
    ├── server
    │   ├── calculatorpb
    │   │   └── calculator.pb.go
    │   ├── go.mod
    │   └── server.go
    └── client
        ├── calculator_grpc_web_pb.js
        └── calculator_pb.js

Добавьте package.json файл в папку client со следующим содержимым.

{
 "name": "grpc-calculator",
 "version": "0.1.0",
 "description": "gRPC-Web Calculator",
 "devDependencies": {
  "@grpc/proto-loader": "^0.3.0",
  "google-protobuf": "^3.6.1",
  "grpc": "^1.15.0",
  "grpc-web": "^1.0.0",
  "webpack": "^4.16.5",
  "webpack-cli": "^3.1.0"
 }
}

Добавьте файл client.js, в котором наша клиентская реализация подключается к нашему серверу gRPC.

Помните, как я говорил вам, что вам не нужно писать код для реализации клиента с gRPC.

Теперь давайте упакуем наш код JavaScript в уменьшенный файл.

npm install
npx webpack client.js

Это должно загрузить все наши зависимости, упомянутые в package.json файле, а затем упаковать client.js в dist/main.js со всеми зависимостями.

Создайте файл index.html, который ссылается на наш dist/main.js файл и выполняет вызов gRPC.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Calculator - gRPC-Web</title>
    <script src="dist/main.js"></script>
</head>
<body>
</body>
</html>

Теперь ваша структура каталогов должна выглядеть так.

grpc-web
    ├── protos
    │   └── calculator.proto
    ├── server
    │   ├── calculatorpb
    │   │   └── calculator.pb.go
    │   ├── go.mod
    │   └── server.go
    └── client
        ├── dist
        │   └── main.js
        ├── calculator_grpc_web_pb.js
        ├── calculator_pb.js
        ├── client.js
        ├── package.json
        └── index.html

Из каталога клиента запустите python сервер, чтобы начать обслуживание наших файлов.

python3 -m http.server 8081 &

Теперь перейдите по адресу http: // localhost: 8081 и откройте консоль.

Вы должны увидеть ошибку в консоли. Вы получите одну или все следующие ошибки.

  1. Запрос на кросс-источник заблокирован
  2. ERR_CONNECTION_REFUSED

Есть две проблемы с нашей текущей реализацией сервера и клиента.

  1. Наш сервер не разрешает запросы CORS и
  2. Даже если он поддерживает CORS, gRPC-web не поддерживает HTTP / 2, поэтому нам нужен прокси-сервер шлюза, например Envoy, для перевода наших запросов HTTP / 1.x, исходящих из gRPC-web, для нашего сервера gRPC.

Исправление ошибок связи с Envoy

Как обсуждалось в предыдущем разделе, нам нужно поместить Envoy между нашим клиентом и сервером для прозрачного преобразования между HTTP / 1.x и HTTP / 2.

Мы будем использовать докер для запуска нашего прокси-сервера. Мы будем использовать стандартный envoy.yaml из gRPC-web hello world demo (конечно, с портами, измененными в соответствии с нашей реализацией).

Создайте файл докера.

FROM envoyproxy/envoy:latest
COPY ./envoy.yaml /etc/envoy/envoy.yaml
CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml

Создайте образ докера.

docker build -t my-envoy:1.0 .

Теперь запустите док-контейнер посланника.

docker run -d -p 8080:8080 -p 9901:9901 --network=host my-envoy:1.0

После запуска envoy нам нужно изменить порт в нашем client.js. Теперь вместо прямого подключения к серверу gRPC на 50551 мы будем подключаться к серверу envoy на 8080.

Снова упакуйте client.js.

npx webpack client.js

Перезагрузите браузер по адресу http: // localhost: 8081 и откройте консоль.

Теперь вы должны увидеть свой результат.

Реализация потоковой передачи на стороне сервера

Отредактируйте файл calculator.proto и добавьте вызов rpc потоковой передачи на стороне сервера для генерации чисел Фибоначчи.

protoc calculator.proto --go_out=plugins=grpc:../server/calculatorpb/Re-compile our proto files for both go and js.
protoc calculator.proto --js_out=import_style=commonjs,binary:../client --grpc-web_out=import_style=commonjs,mode=grpcwebtext:../client

Добавьте реализацию сервера в server.go.

Добавьте потоковый вызов в наш client.js.

Повторно упаковать клиента.

npx webpack client.js

Перезагрузите браузер, и вы должны увидеть, как числа Фибоначчи передаются вашему клиенту.

Заключение

gRPC-web - это действительно зрелая спецификация для подключения к выходу из API gRPC непосредственно из браузеров без какого-либо промежуточного интерфейса REST. Поскольку микросервисы все чаще используют gRPC, gRPC-web действительно хорошо вписывается в мир бесплатного API REST.

Если вы обнаружите какую-либо ошибку в моем коде или у вас возникнут какие-либо вопросы, не стесняйтесь оставлять комментарий.

А до тех пор счастливого кодирования! :)