gRPC - это независимый от языков, независимый от платформ фреймворк для удаленного вызова процедур (RPC) и набор инструментов, разработанный в Google. Они позволяют вам определять сервис, используя Protocol Buffers, мощный набор инструментов и язык бинарой сериализации. С их помощью из определения вы можете генерировать идиоматические основы клиента и сервера для любого языка.

В этой лабораторной работе вы научитесь тому, как делать Node.js сервис, предоставляющий API через gRPC фреймворк. Вы будете взаимодействовать с этим сервисом, используя клиент командной строки, написанный на Go, который использует тоже описание сервиса, что и Node.js сервис. Наконец, вы напишите Node.js клиент командной строки для gRPC сервиса.

Что вы узнаете

Что вам понадобиться

Самостоятельная настройка среды

Если у вас еще нет аккаунта Google (Gmail или Google Apps), вы должны создать новый. Войдите в консоль Google Cloud Platform (console.cloud.google.com) и создайте новый проект:

Запомните ID проекта, уникальное имя среди всех проектов Google Cloud (имя, которое вы видите выше, уже занято, простите!). Оно будет использоваться дальше в лабораторной как PROJECT_ID.

Дальше вам надо включить биллинг в Developers Console для того, чтобы использовать ресурсы Google Cloud и включить Container Engine API.

Эта лабораторная работа не будет стоить вам больше пары долларов, если вы, конечно, не решите использовать больше ресурсов или оставите их в рабочем состоянии (посмотрите раздел "Очистка" в конце этой статьи). Цены на Google Container Engine вы можете найти тут.

Новые пользователи Google Cloud Platform имеют право на получение бесплатного пробного периода на $300.

Google Cloud Shell

В то время как с Google Cloud и Kubernetes вы можете работать удаленно с вашего компьютера, в этом уроке мы будем использовать Google Cloud Shell, среду командной строки, работающую в облаке Cloud.

This Debian-based virtual machine is loaded with all the development tools you'll need. It offers a persistent 5GB home directory, and runs on the Google Cloud, greatly enhancing network performance and authentication. This means that all you will need for this codelab is a browser (yes, it works on a Chromebook).

To activate Google Cloud Shell, from the developer console simply click the button on the top right-hand side (it should only take a few moments to provision and connect to the environment):

Once connected to the cloud shell, you should see that you are already authenticated and that the project is already set to your PROJECT_ID :

gcloud auth list

Command output

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

Command output

[core]
project = <PROJECT_ID>

If for some reason the project is not set, simply issue the following command :

gcloud config set project <PROJECT_ID>

Looking for you PROJECT_ID? Check out what ID you used in the setup steps or look it up in the console dashboard :

IMPORTANT. Finally, set the default zone and project configuration:

gcloud config set compute/zone us-central1-f

You can pick and choose different zones too. Learn more about zones in Regions & Zones documentation.

Чтобы запустить простое приложение, вам потребуются следующие зависимости:

Все это предустановлено на вашей оболочке Google Cloud Shell, которой мы будем пользоваться в этой работе.

Перейдите в Developer Console, она должна автоматически залогинить вас на основе вашего логина в Chromebook. Если у вас запросят имя/пароль, введите данные, которые мы вас предоставили.

Вам надо принять условия использования до того, как вы перейдете в Developer Console.

В верхнем правом углу вы увидите иконку Cloud Shell в виде символа . Кликните для того, чтобы открыть вашу собственную сессию Cloud Shell. Все следующие команды надо выполнять в сессии Cloud Shell.

Вы можете открыть дополнительные сессии Cloud Shell кликнув на "+" в закладках сессии Cloud Shell .

Итак, вы готовы приступить к работе!

Итого

В этом разделе вы узнали, как настроить вашу среду разработки.

Далее

Вы скачаете и запустите тестовое приложение.

Вы можете скачать весь тестовый код для работы в Cloud Shell...

Download Zip

...или клонировать репозиторий GitHub из командной строки:

git clone https://github.com/googlecodelabs/cloud-grpc

В тестовом проекте:

Имя

Описание

client.go

Клиент командной строки для API сервера.

books/books.pb.go

Библиотека go для книжного gRPC сервиса.

Запускаем тестовое приложение

В папке тестового приложения есть client.go, клиент командной строки для взаимодействия с gRPC сервисом, который вы создадите в ходе этой лабораторной работы.

Чтобы запустить клиента, сначала установите пакет Go grpc:

$ go get google.golang.org/grpc

Теперь, из директории проекта, запустите клиента командной строки, без аргументов, просто чтобы посмотреть доступные команды:

$ go run client.go 
client.go is a command-line client for this codelab's gRPC service

Usage:
  client.go list                            List all books
  client.go insert <id> <title> <author>    Insert a book
  client.go get <id>                        Get a book by its ID
  client.go delete <id>                     Delete a book by its ID
  client.go watch                           Watch for inserted books

Попробуйте выполнить одну из доступных команд:

$ go run client.go list

Через несколько секунд вы увидите список ошибок, так как node gRPC сервера еще не существует!

Давайте исправим это!

Итого

В этом разделе вы настроили и запустили тестовое приложение.

Далее

Вы сделаете Node.js gRPC сервис, который выводит список книг.

Сейчас вы напишите код для создания Node.js gRPC сервиса, выводящего список книг.

gRPC сервис определяется в файлах .proto с помощью языка Protocol Buffer.

Язык Protocol Buffer используется для определения сервисов и типов сообщений.

Давайте начнем с определения сервиса для книг!

В директории проекта cloud-grpc/start создайте новый файл books.proto и добавьте в него следующее:

books.proto

syntax = "proto3";

package books;

service BookService {}

Этот код определяет новый сервис с именем BookService, используя версию proto3 языка Protocol Buffer. Это последняя версия Protocol Buffer и она рекомендована для использования с gRPC.

Чтобы запустить сервис с node, сперва надо установить пакет grpc:

$ npm install grpc

Теперь давайте соберем Node.js сервис, начнем с нового файла server.js с таким содержанием:

server.js

var grpc = require('grpc');

var booksProto = grpc.load('books.proto');

var server = new grpc.Server();

server.addProtoService(booksProto.books.BookService.service, {});

server.bind('0.0.0.0:50051',
  grpc.ServerCredentials.createInsecure());
server.start();

grpc.load возвращает объект, представляющий .proto.

grpc.Server создает новый объект сервера gRPC

server.addProtoService принимает дескрипшн proto сервиса, который мы создали с помощью grpc.load(...), и карту имен методов для реализации методов предоставляемых сервисов.

Наконец, server привязывается к порту 50051, предоставляя учетный объект (в нашем случае мы создали его вызовом grpc.ServerCredentials.createInsecure() и обращается к start() для начала прослушивания входящих запросов!

Откройте вторую сессию Cloud Shell, в которой мы запустим сервис. Перейдите в вашу рабочую директорию и запустите сервер:

$ node server.js

Теперь снова запустите клиент командной строки go gRPC в другой сессии Cloud Shell Session, чтобы протестировать серверный API:

$ go run client.go list

В этот раз вы увидите другую ошибку:

Отлично! Она означает, что сервер запущен и go клиент общается с ним через gRPC!

Давайте исправим ошибку и реализуем метод List!

Список книг

В директории проекта отредактируйте books.proto и обновите BookService следующим кодом:

books.proto

syntax = "proto3";

package books;

service BookService {
  rpc List (Empty) returns (BookList) {}
}

message Empty {} 

message Book {
  int32 id = 1;
  string title = 2;
  string author = 3;
}

message BookList {
  repeated Book books = 1;
}

Каждый rpc метод сервиса принимает сообщение (request) и возвращает сообщение (response).

Выполнение запроса к List не требует параметров, поэтому у сообщения запроса к List нет полей (определено как Empty сообщение).

Сообщение Book представляет объект одной книги с полями id, title и author.

List возвращает список repeated сообщений Book. Repeated определяет, что это сообщение может повторяться любое количество раз.

Теперь обновим приложение Node.js для ответов на вызовы метода List в BookService.

Отредактируйте файл server.js и добавьте следующий код, заменив текущее var server на следующее:

server.js

// In-memory array of book objects
var books = [ 
  { id: 123, title: 'A Tale of Two Cities', author: 'Charles Dickens' }
];

var server = new grpc.Server();
server.addProtoService(booksProto.books.BookService.service, {
    list: function(call, callback) {
        callback(null, books);
    }
});

Для регистрации обработчиков методов gRPC сервиса, для каждого в конструктор Server передаются функции обработки. Когда метод вызывается, обрабатывающая функция вызывается с объектом call, представляющим запрашивающее сообщение. Чтобы ответить на метод, вызовите callback, предоставив объект ошибки (или null) и объект, представляющий ответное сообщение. В этом случае мы вернем объект JavaScript с полями, определенными в book.proto для сообщения типа Book.

Это реализовано в rpc вызове List, возвращающем сообщение Book.

Чтобы протестировать это, остановите запущенный node процесс в вашей второй сессии Cloud Shell, нажав CTRL-C и запустив его опять:

$ node server.js

Переключитесь обратно в другую сессию и снова запустите go gRPC клиент командной строки:

$ go run client.go list

Теперь вы должны увидеть список книг!

Got 1 books.
{
  "books": [
    {
      "id": 123,
      "title": "A Tale of Two Cities",
      "author": "Charles Dickens"
    }
  ]
}

Итого

В этом разделе вы реализовали gRPC сервис, который выводит список книг.

Далее

Давайте добавим книги с помощью gRPC вызова.

В этом разделе вы напишите код для добавления новых объектов Book через gRPC сервис.

Для начала отредактируйте books.proto и обновите BookService вот так:

books.proto

service BookService {
  rpc List (Empty) returns (BookList) {}
  // add the following line
  rpc Insert (Book) returns (Empty) {}
}

Это определяет новый rpc вызов Insert, который берет сообщение Book при запросе и возвращает ответ Empty.

Для реализации метода Insert в сервере, отредактируйте server.js и добавьте метод вставки в карту функций в addProtoService:

server.js

server.addProtoService(booksProto.books.BookService.service, {
    list: function(call, callback) {
        callback(null, books);
    },
    // add the insert method
    insert: function(call, callback) {
        books.push(call.request);
        callback(null, {});
    }
});

Добавленная функция insert реализует rpc вызов Insert, добавляя полученное сообщение Book к массиву books и возвращает сообщение Empty.

Обрабатывающие функции получают доступ к сообщению запроса через call.request. В данном случае call.request это JavaScript объект с полями id, title и author, представляющими сообщение Book.

Для теста перезапустите node сервер и затем выполните в go gRPC клиенте командной строки команду insert, передав id, title и author в качестве аргументов:

$ go run client.go insert 2 "The Three Musketeers" "Alexandre Dumas"

Вы должны увидеть пустой ответ:

Server response:
{}

Чтобы проверить добавление книги, снова выполните команду list для получения списка книг:

$ go run client.go list

Теперь у вас должно быть 2 книги!

Got 2 books.
{
  "books": [
    {
      "id": 123,
      "title": "A Tale of Two Cities",
      "author": "Charles Dickens"
    },
    {
      "id": 2,
      "title": "The Three Musketeers",
      "author": "Alexandre Dumas"
    }
  ]
}

Итого

В этом разделе вы расширили работу gRPC сервиса и реализовали добавление книг.

Далее

Вы продолжите разработку сервиса и сделаете удаление отдельных книг.

В этом разделе вы напишите код для получения и удаления объекта Book по id при помощи gRPC сервиса.

Для начала, отредактируйте books.proto и обновите BookService вот так:

books.proto

service BookService {
  rpc List (Empty) returns (BookList) {}
  rpc Insert (Book) returns (Empty) {}
  // add the following line
  rpc Get (BookIdRequest) returns (Book) {}
}

// add the message definition below
message BookIdRequest {
  int32 id = 1;
}

Это определяет новый rpc вызов Get, который берет BookIdRequest из запроса и возвращает Book в ответе.

Тип сообщения BookIdRequest определяется для запроса и содержит только id книги.

Для реализации метода Get на сервере, отредактируйте server.js и добавьте следующую функцию обработки get:

server.js

server.addProtoService(booksProto.books.BookService.service, {
    // ...
    // add the following get method
    get: function(call, callback) {
        for (var i = 0; i < books.length; i++)
            if (books[i].id == call.request.id)
                return callback(null, books[i]);
        callback({
            code: grpc.status.NOT_FOUND,
            details: 'Not found'
        });
    }
});

Если массив books содержит книгу с запрошенным id, то она возвращается. Если такой книги не нашлось, то возвращается ошибка NOT_FOUND.

Чтобы протестировать сделанное, перезапустите node сервер и затем выполните в go gRPC клиенте команду get, передав ей id в качестве аргумента:

$ go run client.go get 123

Вы должны увидеть ответ с книгой!

Server response:
{
  "id": 123,
  "title": "A Tale of Two Cities",
  "author": "Charles Dickens"
}

А теперь попробуйте запросить книгу, которая не существует:

$ go run client.go get 404

Вы должны увидеть сообщение об ошибке:

Удаление книг

Теперь давайте напишем код для удаления книги по id.

Отредактируйте books.proto и добавьте следующий метод Delete:

books.proto

service BookService {
  // ...
  // add the delete method definition
  rpc Delete (BookIdRequest) returns (Empty) {}
}

Теперь отредактируйте server.js и добавьте такую функцию обработки delete:

server.js

server.addProtoService(booksProto.books.BookService.service, {
    // ...
    // add the following delete method
    delete: function(call, callback) {
        for (var i = 0; i < books.length; i++) {
            if (books[i].id == call.request.id) {
                books.splice(i, 1);
                return callback(null, {});
            }
        }
        callback({
            code: grpc.status.NOT_FOUND,
            details: 'Not found'
        });
    }
});

Если массив books содержит книгу с запрошенным id, то книга удаляется, в противном случае возвращается ошибка NOT_FOUND.

Чтобы протестировать это, перезапустите node сервер и затем выполните в go gRPC клиенте команду для удаления книги:

$ go run client.go list
Server sent 1 book(s).
{
  "books": [
    {
      "id": 123,
      "title": "A Tale of Two Cities",
      "author": "Charles Dickens"
    }
  ]
}

$ go run client.go delete 123
Server response:
{}

$ go run client.go list
Server sent 0 book(s).
{}

$ go run client.go delete 123
Delete book (123): rpc error: code = 5 desc = "Not found"

Прекрасно!

Вы реализовали полнофункциональный gRPC сервис, который может выводить список книг, добавлять и удалять их, а также отдавать информацию о конкретной книге!

Итого

В этом разделе вы реализовали получение и удаление книг в gRPC сервисе.

Далее

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

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

gRPC поддерживает потоковую семантику, в которой клиент или сервер (или оба) посылают поток сообщений в одном RPC вызове. Самый общий случай - это Двунаправленный стриминг (Bidirectional Streaming), в нем один gRPC вызов устанавливает поток, в котором клиент и сервер могут отправлять сообщения друг другу.

Для начала отредактируйте books.proto и добавьте такой rpc метод Watch к BookService:

books.proto

service BookService {
  // ...
  rpc Watch (Empty) returns (stream Book) {}
}

Когда клиент обращается к методу Watch, он открывает поток и сервер может стримить сообщения Book при добавлении книг.

Чтобы реализовать метод Watch на сервере, отредактируйте server.js и добавьте переменную bookStream и обработчик watch:

server.js

var bookStream;

var server = new grpc.Server();
server.addProtoService(booksProto.books.BookService.service, {
    // ...
    watch: function(stream) {
        bookStream = stream;
    }
});

Функции обработки для потоковых rpc методов вызываются с записываемым объектом stream.

Для стриминга сообщений клиенту вызывается функция потока write().

Отредактируйте server.js и обновите функцию insert для стриминга сообщений Book клиенту при добавлении книги:

server.js

var bookStream;

var server = new grpc.Server();
server.addProtoService(booksProto.books.BookService.service, {
    // ...
    insert: function(call, callback) {
        var book = call.request;
        books.push(book);
        // add the following to the insert method
        if (bookStream)
            bookStream.write(book);
        callback(null, {});
    },
    // ...
});

Чтобы протестировать сделанное, перезапустите node сервер и затем выполните в go gRPC клиенте командной строки команду watch в третьей сессии Cloud Shell:

$ go run client.go watch

Теперь выполните в go gRPC клиенте команду insert для добавления книги - в вашей главной Cloud Shell сессии:

$ go run client.go insert 2 "The Three Musketeers" "Alexandre Dumas"

Проверьте сессию Cloud Shell, в которой запущен процесс client.go watch. В ней должна появиться добавленная книга!

$ go run client.go watch
Server stream data received:
{
  "id": 2,
  "title": "The Three Musketeers",
  "author": "Alexandre Dumas"
}

Итого

В этом разделе вы добавили потоковую gRPC точку к сервису для стриминга добавленных книг на подключенный клиент.

Далее

Вы напишите клиента командной строки для взаимодействия с вашим gRPC сервисом.

В этом шаге вы напишите код для реализации клиента командной строки на Node.js, который обращается к вашему gRPC сервису.

В результате вы получите функциональный эквивалент скрипта client.go, который вы все время использовали в этой лабораторной работе!

Начнем снова с запуска gRPC сервера (если он еще не запущен):

$ node server.js

Теперь создадим новый файл client.js в директории проекта и добавим в него следующее:

client.js

var grpc = require('grpc');

var booksProto = grpc.load('books.proto');

var client = new booksProto.books.BookService('127.0.0.1:50051', 
  grpc.credentials.createInsecure());

client.list({}, function(error, books) {
  if (error)
    console.log('Error: ', error);
  else
    console.log(books);
});

Тут определяется требование node модуля grpc и загружается books.proto (точно так же, как вы делали в server.js ранее).

Объект client для gRPC сервиса создается обращением к BookService конструктору, который динамически создается из определения сервиса в books.proto.

Функция list() берет запрашиваемый объект сообщения как параметр ({} в данном случае для представления Empty сообщения), затем идет callback функция, которая будет вызываться с объектом error (или null) и ответным объектом сообщения (в данном случае сообщением Book).

books.proto

service BookService {
  rpc List (Empty) returns (BookList) {}
  rpc Insert (Book) returns (Empty) {}
  rpc Get (BookIdRequest) returns (Book) {}
  rpc Delete (BookIdRequest) returns (Empty) {}
  rpc Watch (Empty) returns (stream Book) {}
}

Это означает, что теперь вы можете делать list(), insert(), get(), delete() и watch() для книг!

Теперь давайте запустим клиент командной строки:

$ node client.js list
{ books: 
   [ { id: 123,
       title: 'A Tale of Two Cities',
       author: 'Charles Dickens' } ] }

Вы должны увидеть список книг!

Далее, для реализации клиента, обновим client.js функциями list(), insert(), get()и delete():

client.js

var grpc = require('grpc');

var booksProto = grpc.load('books.proto');

var client = new booksProto.books.BookService('127.0.0.1:50051', 
  grpc.Credentials.createInsecure());

function printResponse(error, response) {
  if (error)
    console.log('Error: ', error);
  else
    console.log(response);
}

function listBooks() {
  client.list({}, function(error, books) {
    printResponse(error, books);
  });
}

function insertBook(id, title, author) {
  var book = { id: parseInt(id), title: title, author: author };
  client.insert(book, function(error, empty) {
    printResponse(error, empty);
  });
}

function getBook(id) {
  client.get({ id: parseInt(id) }, function(error, book) {
    printResponse(error, book);
  });
}

function deleteBook(id) {
  client.delete({ id: parseInt(id) }, function(error, empty) {
    printResponse(error, empty);
  });
}

Чтобы сделать функцию watch(), которая получает поток сообщений Book, зарегистрируем обработчик событий:

client.js

function watchBooks() {
  var call = client.watch({});
  call.on('data', function(book) {
    console.log(book);
  });
}

Вызов функции on('data') будет происходить с объектом сообщения Book при добавлении книги.

Наконец, добавим код для парсинга аргументов командной строки:

client.js

var processName = process.argv.shift();
var scriptName = process.argv.shift();
var command = process.argv.shift();

if (command == 'list')
  listBooks();
else if (command == 'insert')
  insertBook(process.argv[0], process.argv[1], process.argv[2]);
else if (command == 'get')
  getBook(process.argv[0]);
else if (command == 'delete')
  deleteBook(process.argv[0]);
else if (command == 'watch')
  watchBooks();

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

$ node client.js list
{ books: 
   [ { id: 123,
       title: 'A Tale of Two Cities',
       author: 'Charles Dickens' } ] }

Теперь все команды, которые работали в client.go, должны работать и в вашем Node.js gRPC клиенте!

$ node client.js insert 2 "The Three Musketeers" "Alexandre Dumas"
{}

$ node client.js list
{ books: 
   [ { id: 123,
       title: 'A Tale of Two Cities',
       author: 'Charles Dickens' },
     { id: 2,
       title: 'The Three Musketeers',
       author: 'Alexandre Dumas' } ] }

$ node client.js delete 123
{}

$ node client.js list
{ books: 
   [ { id: 2,
       title: 'The Three Musketeers',
       author: 'Alexandre Dumas' } ] }

$ node client.js get 2
{ id: 2,
  title: 'The Three Musketeers',
  author: 'Alexandre Dumas' }

Итого

В этом разделе вы написали клиента командной строки на Node.js, который взаимодействует с вашим gRPC сервисом.

Что мы изучили:

Далее:

Оставьте свой отзыв