Этот девлог рассказывает о путешествии grpcxx — попытке создать лучший gRPC Server API с использованием современного C++ (C++20).

Следуя изменениям, которые я внес для отправки запросов RPC в службу (Журнал разработчиков № 3), я начал работать над обновлением логики сервера{} для обработки входящих запросов от клиентов.

Сначала мне нужно было обновить класс шаблона service{}, чтобы я мог зафиксировать имя службы (протокол gRPC HTTP/2 предлагает всем запросам установить для заголовка :path HTTP/2 значение /{service name}/{method name}) . Это было сделано аналогично захвату имени метода в классе шаблона rpc{} путем введения параметра шаблона fixed_string. например

template <fixed_string N, concepts::rpc_type... R>
class service {
public:
  constexpr std::string_view name() const noexcept { return {N}; };
};

Чтобы добавить службу на сервер, я реализовал шаблонную функцию <typename S> void add(S &s);.

Наконец, я обновил обратный вызов server{}, который прослушивает события HTTP/2, чтобы извлечь имя службы из заголовка :path HTTP/2 и отправить соответствующую службу для обработки запроса. Если службы, которая соответствовала бы названию службы, нет, она просто вернет статус NOT_FOUND gRPC.

Почему не работает!?

Я мог бы использовать curl для проверки возврата ответа HTTP/2, но если я использую grpcurl, это не сработает.

Спойлер — я догоняю свою неправильную интерпретацию протокола gRPC, о которой я кратко упомянул в devlog #1.

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

Сообщение с префиксом длины — это сериализованное сообщение protobuf с префиксом из 5 байтов метаданных. 5-байтовый префикс включает 1 байт сжатого флага и 4 байта (целое число без знака с обратным порядком байтов), указывающих размер сериализованного сообщения protobuf.

Вторая ошибка, на отладку которой я потратил больше всего времени, заключалась в том, что протокол gRPC HTTP/2 чувствителен к порядку кадров HTTP/2. т. е. ответ gRPC должен сначала отправить кадр заголовка HTTP/2, за которым следует кадр данных HTTP/2 и, наконец, еще один кадр заголовка HTTP/2 с заголовком состояния gRPC. например

HEADERS (flags = END_HEADERS)
:status = 200
content-type = application/grpc

DATA
<Length-Prefixed Message>

HEADERS (flags = END_STREAM, END_HEADERS)
grpc-status = 0 # OK

Чтобы добавить к моей ошибке, я вызывал функции nghttp2_submit_headers() и nghttp2_submit_data(), но не вызывал nghttp2_session_send() для фактической записи ожидающих кадров в сеть. Это означало, что хотя в коде я пытался отправить заголовки, данные и заголовки, nghttp2 объединил два кадры заголовков и отправляются только заголовки, за которыми следуют данные.

После долгих отладок и исправлений моих ошибок я заставил сервер успешно отправлять запросы и отправлять ответ 🎉.

В следующий раз, Привет Мир! 👨‍💻