Этот девлог рассказывает о путешествии 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 объединил два кадры заголовков и отправляются только заголовки, за которыми следуют данные.
После долгих отладок и исправлений моих ошибок я заставил сервер успешно отправлять запросы и отправлять ответ 🎉.
В следующий раз, Привет Мир! 👨💻