Ответ
Утверждение, что Protobuf не может работать поверх HTTP/1.1, неверно. Он может, но такое сочетание неэффективно и сводит на нет многие преимущества как Protobuf, так и современных сетевых протоколов.
Основные проблемы:
-
Отсутствие мультиплексирования. HTTP/1.1 обрабатывает запросы строго последовательно (проблема
Head-of-line blocking
). Это мешает эффективной потоковой передаче данных (стримингу), для которой хорошо подходит Protobuf. -
Текстовая природа протокола. HTTP/1.1 — текстовый протокол. Для передачи бинарных данных Protobuf их нужно кодировать (например, в
Base64
), что увеличивает размер запроса на ~33% и требует дополнительных затрат CPU на кодирование/декодирование. -
Большие заголовки. Заголовки в HTTP/1.1 не сжимаются и отправляются с каждым запросом, что создает значительные накладные расходы, особенно для маленьких и частых сообщений Protobuf.
Пример такой реализации:
// Клиент отправляет Protobuf в теле POST-запроса
reqBody, _ := proto.Marshal(&pb.MyRequest{Data: "hello"})
req, _ := http.NewRequest("POST", url, bytes.NewReader(reqBody))
// Указываем Content-Type, чтобы сервер знал, как парсить тело
req.Header.Set("Content-Type", "application/x-protobuf")
client.Do(req)
// Сервер читает тело и десериализует его
body, _ := io.ReadAll(r.Body)
var req pb.MyRequest
proto.Unmarshal(body, &req)
Решение: gRPC и HTTP/2
Именно для решения этих проблем был создан gRPC, который использует Protobuf поверх HTTP/2.
- HTTP/2 предлагает бинарные фреймы, мультиплексирование и сжатие заголовков, что идеально подходит для передачи бинарных сообщений Protobuf.
- gRPC добавляет к этому строгую типизацию API, автоматическую генерацию кода клиента и сервера и нативную поддержку стриминга.