В деле удалённого вызова процедур дела уже давно обстоят в точности как в известном комиксе «14 стандартов» — чего только тут не напридумано: древние DCOM и Corba, странные SOAP и .NET Remoting, современные REST и AMQP (да, я знаю, что кое-что из этого формально не RPC, для того чтобы обсудить терминологию даже вот специальный топик недавно создали, тем ни менее всё это используется как RPC, а если что-то выглядит, как утка и плавает, как утка — ну, вы в курсе).
И конечно же, в полном соответствии со сценарием комикса, на рынок пришел Google и заявил что вот теперь наконец он создал ещё один, последний и самый правильный стандарт RPC. Google можно понять — продолжать в 21-ом веке гонять петабайты данных по старому и неэффективному HTTP+REST, теряя на каждом байте деньги — просто глупо. В то же время взять чужой стандарт и сказать «мы не смогли придумать ничего лучше» — совершенно не в их стиле.
Поэтому, встречайте, gRPC, что расшифровывается как «gRPC Remote Procedure Calls» — новый фреймворк для удалённого вызова процедур от Google. В этой статье мы поговорим о том, почему же он, в отличии от предыдущих «14 стандартов» всё-таки захватит мир (ну или хотя бы его часть), попробуем собрать билд gRPC под Windows + Visual Studio (и даже не говорите мне, что инструкция не нужна — в официальной документации упущено штук 5 важных шагов, без которых ничего не собирается), а также попробуем написать простенький сервис и клиент, обменивающиеся запросами и ответами.
Зачем нужен ещё один стандарт?
Прежде всего давайте оглянемся вокруг. Что мы видим? Мы видим REST + HTTP/1.1. Нет, есть всякое, но именно эта туча закрывает добрых три четверти небосвода клиент-серверных коммуникаций. Присмотревшись ещё чуть пристальнее, мы видим, что REST в 95% случаев вырождается в CRUD.
В итоге мы имеем:
- Неэффективность протокола HTTP/1.1 — несжатые заголовки, отсутствие полноценной двусторонней связи, неэффективный подход к использованию ресурсов ОС, лишний трафик, лишние задержки.
- Необходимость натягивать нашу модель данных и событий на REST+CRUD, что часто получается, как воздушный шарик на глобус и вынуждает Яндекс писать вот такие, бесспорно, очень хорошие статьи, которые, однако, были бы не нужны, если бы людям не приходилось думать «Чем же вызвать заклинание для призыва элементаля — PUT’ом или POST’ом? И какой же HTTP-код вернуть, чтобы он означал „Перейдите на 3 клетки вперёд и тяните новую карту“ ?»
Именно с этого места и начинается gRPC. Итак, из коробки мы имеем:
- Protobuf в качестве инструмента описания типов данных и сериализации. Очень классная и хорошо зарекомендовавшая себя на практике штука. Собственно говоря, те, кому была нужна производительность — и раньше брали Protobuf, а дальше уже отдельно заморачивались транспортом. Теперь всё в комплекте.
- HTTP/2 в качестве транспорта. И это невероятно мощный ход! Вся прелесть полного сжатия данных, контроля трафика, инициации событий с сервера, переиспользования одного cокета для нескольких параллельных запросов — красотища.
- Статические пути — никаких больше «сервис/коллекция/ресурс/запрос? параметр=значение». Теперь только «сервис», а что внутри — описывайте в терминах вашей модели и её событий.
- Никакого привязывания методов к HTTP-методам, никакого привязывания возвращаемых значений к HTTP-статусам. Пишите, что хотите.
- SSL/TLS, OAuth 2.0, аутентификация через сервисы Google, плюс можно прикрутить свою (например, двухфакторную)
- Поддержка 9-ти языков: C, C++, Java, Go, Node.js, Python, Ruby, Objective-C, PHP, C# плюс, конечно, никто не запрещает взять и реализовать свою версию хоть для брейнфака.
- Поддержка gRPC в публичных API от Google. Уже работает для некоторых сервисов. Нет, REST-версии, конечно, тоже останутся. Но посудите сами, если у вас будет выбор — использовать, скажем, из мобильного приложения REST-версию, отдающие данные за 1 сек или с теми же затратами на разработку взять gRPC-версию, работающую 0.5 сек — что вы выберете? А что выберет ваш конкурент?
Сборка gRPC
Нам понадобитья:
- Git
- Visual Studio 2013 + Nuget
- CMake
Забираем код
- Забираем репозипорий gRPC с Гитхаба
- Выполняем команду
git submodule update --init
— это нужно для того, чтобы скачать зависимости (protobuf, openssl и т.д.).
Собираем Protobuf
- Переходим в папку grpc\third_party\protobuf\cmake и создаём там папку build, переходим в неё.
- Выполняем команду
cmake -G «Visual Studio 12 2013» -DBUILD_TESTING=OFF… - Открываем созданный на предыдущем шаге файл protobuf.sln в Visual Studio и собираем (F7).
На этом этапе мы получаем ценные артефакты — утилиту protoc.exe, которая понадобиться нам для генерации кода сериализации\десериализации данных и lib-файлы, которая будут нужны при линковке gRPC. - Копируем папку grpc\third_party\protobuf\cmake\build\Debug в папку grpc\third_party\protobuf\cmake.
Ещё раз — папку Debug нужно скопировать на 1 уровень выше. Это какая-то неконсистентность в документациях gRPC и Protobuf. В Protobuf говорится, что всё билдить надо в папке build, а вот исходники проектов gRPC ничего об этой папке не знают и ищут библиотеки Protobuf прямо в grpc\third_party\protobuf\cmake\Debug
Собираем gRPC
- Открываем файл grpc\vsprojects\grpc_protoc_plugins.sln и собираем его.
Если вы верно прошли сборку Protobuf на предыдущем этапе — всё должно пройти гладко. Теперь у вас есть плагины к protoc.exe, которые позволяют ему не только генерировать код сериализации\десериализации, но и добавлять в него функционал gRPC (собственно говоря, удалённый вызов процедур). Плагины и protoc.exe нужно положить в одну папку, например, в grpc\vsprojects\Debug. - Открываем файл grpc\vsprojects\grpc.sln и собираем его.
По ходу сборку должен запуститься Nuget и скачать необходимые зависимости (openssl, zlib). Если у вас нет Nuget или он почему-то не скачал зависимости — будут проблемы.
По окончанию билда у нас появятся все необходимые библиотеки, которые мы сможем использовать в нашем проекте для коммуникаций через gRPC.
Наш проект
Давайте напишем такой себе API для Хабрахабра с применением gRPC
Методы у нас будут такие:
- GetKarma будет получать строку с именем пользователя, а возвращать дробное число со значением его кармы
- PostArticle будет получать запрос на создание новой статьи со всеми её метаданными, а возвращать результат публикации — структуру со ссылкой на статью, временем публикации ну и текстом ошибки, если публикация не удалась
Это всё нам нужно описать в терминах gRPC. Это будет выглядеть как-то так (описание типов можно посмотреть в документации на protobuf):
syntax = "proto3"; package HabrahabrApi; message KarmaRequest { string username = 1; } message KarmaResponse { string username = 1; float karma = 2; } message PostArticleRequest { string title = 1; string body = 2; repeated string tag = 3; repeated string hub = 4; } message PostArticleResponse { bool posted = 1; string url = 2; string time = 3; string error_code = 4; } service HabrApi { rpc GetKarma(KarmaRequest) returns (KarmaResponse) {} rpc PostArticle(PostArticleRequest) returns (PostArticleResponse) {} }
Переходим в папку grpc\vsprojects\Debug и запускаем там 2 команды (кстати, обратите внимание, в официальной документации в этом месте ошибка, неверные аргументы):
protoc --grpc_out=. --plugin=protoc-gen-grpc=grpc_cpp_plugin.exe habr.proto protoc --cpp_out=. habr.proto
На выходе мы получим 4 файла:
- habr.pb.h
- habr.pb.cc
- habr.grpc.pb.h
- habr.grpc.pb.cc
Это, как не сложно догадаться, заготовки наших будущих клиента и сервиса, которые смогут обмениваться сообщениями по описанному выше протоколу.
Давайте уже создадим проект!
- Создаём в Visual Studio новый solution, назовём его HabrAPI.
- Добавляем в него два консольных приложения — HabrServer и HabrClient.
- Добавляем в них сгенерированные на предыдущем шаге h и сс-файлы. В сервер надо включать все 4, в клиент — только habr.pb.h и habr.pb.cc.
- Добавляем в настройках проектов в Additional Include Directories путь к папкам grpc\third_party\protobuf\src и grpc\include
- Добавляем в настройках проектов в Additional Library Directories путь к grpc\third_party\protobuf\cmake\Debug
- Добавляем в настройках проектов в Additional Dependencies библиотеку libprotobuf.lib
- Выставляем тип линковки таким же, с каким был собран Protobuf (свойство Runtime Library на вкладке Code Generation). В этом месте может оказаться, что вы не собрали Protobuf в нужной вам конфигурации, и придётся вернуться и пересобрать его. Я выбирал и там и там /MTd.
- Добавляем через Nuget зависимости на zlib и openssl.
Теперь у нас всё собирается. Правда, ничего пока ещё не работает.
Клиент
Здесь всё просто. Во-первых, нам нужно создать класс, унаследованный от заглушки, сгенерированной в habr.pb.h. Во-вторых, реализовать в нём методы GetKarma и PostArticle. В-третьих, вызывать их и, к примеру, выводить результаты в консоль. Получится как-то так:
#include <iostream> #include <memory> #include <string> #include <grpc/grpc.h> #include <grpc++/channel.h> #include <grpc++/client_context.h> #include <grpc++/create_channel.h> #include <grpc++/credentials.h> #include "habr.grpc.pb.h" using grpc::Channel; using grpc::ChannelArguments; using grpc::ClientContext; using grpc::Status; using HabrahabrApi::KarmaRequest; using HabrahabrApi::KarmaResponse; using HabrahabrApi::PostArticleRequest; using HabrahabrApi::PostArticleResponse; using HabrahabrApi::HabrApi; class HabrahabrClient { public: HabrahabrClient(std::shared_ptr<Channel> channel) : stub_(HabrApi::NewStub(channel)) {} float GetKarma(const std::string& username) { KarmaRequest request; request.set_username(username); KarmaResponse reply; ClientContext context; Status status = stub_->GetKarma(&context, request, &reply); if (status.ok()) { return reply.karma(); } else { return 0; } } bool PostArticle(const std::string& username) { PostArticleRequest request; request.set_title("Article about gRPC"); request.set_body("bla-bla-bla"); request.set_tag("UFO"); request.set_hab("Infopulse"); PostArticleResponse reply; ClientContext context; Status status = stub_->PostArticle(&context, request, &reply); return status.ok() && reply.posted(); } private: std::unique_ptr<HabrApi::Stub> stub_; }; int main(int argc, char** argv) { HabrahabrClient client( grpc::CreateChannel("localhost:50051", grpc::InsecureCredentials(), ChannelArguments())); std::string user("tangro"); std::string reply = client.GetKarma(user); std::cout << "Karma received: " << reply << std::endl; return 0; }
Сервер
С сервером похожая история — мы наследуемся от класса сервиса, сгенерированного в habr.grpc.pb.h и реализуем его методы. Дальше мы запускаем слушателя на определённом порту, ну и ждём клиентов. Как-то вот так:
#include <iostream> #include <memory> #include <string> #include <grpc/grpc.h> #include <grpc++/server.h> #include <grpc++/server_builder.h> #include <grpc++/server_context.h> #include <grpc++/server_credentials.h> #include "habr.grpc.pb.h" using grpc::Server; using grpc::ServerBuilder; using grpc::ServerContext; using grpc::Status; using HabrahabrApi::KarmaRequest; using HabrahabrApi::KarmaResponse; using HabrahabrApi::PostArticleRequest; using HabrahabrApi::PostArticleResponse; using HabrahabrApi::HabrApi; class HabrahabrServiceImpl final : public HabrApi::Service { Status GetKarma(ServerContext* context, const KarmaRequest* request, KarmaResponse* reply) override { reply->set_karma(42); return Status::OK; } Status PostArticle(ServerContext* context, const PostArticleRequest* request, PostArticleResponse* reply) override { reply->set_posted(true); reply->set_url("some_url"); return Status::OK; } }; void RunServer() { std::string server_address("0.0.0.0:50051"); HabrahabrServiceImpl service; ServerBuilder builder; builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); builder.RegisterService(&service); std::unique_ptr<Server> server(builder.BuildAndStart()); std::cout << "Server listening on " << server_address << std::endl; server->Wait(); } int main(int argc, char** argv) { RunServer(); return 0; }
Удачи вам в использовании gRPC.
ссылка на оригинал статьи http://habrahabr.ru/post/265805/