Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 1 из 25 ~15 мин +50 XP

Введение в Connect RPC

Что такое Connect RPC и зачем он появился, если gRPC уже решил проблему RPC в Go? У gRPC есть ахиллесова пята — браузер: из JavaScript обычный gRPC-сервер вызвать нельзя. Разберём, почему, что такое gRPC-Web и чем Connect от Buf отличается: один handler на net/http понимает сразу три протокола.

Содержание урока · 15 разделов

Начну с ситуации, в которой оказывается почти каждый, кто прошёл курс по gRPC. Сервис на google.golang.org/grpc радует: контракт в .proto, заглушки сгенерированы, дедлайны и стриминг работают, всё тестировалось. Потом приходит фронтенд-команда и спрашивает: «А можно нам те же методы прямо из браузера?» И здесь понижение температуры: нельзя. Браузерный JavaScript не умеет говорить по gRPC — ни fetch, ни XMLHttpRequest не дают доступа к HTTP/2-трейлерам, где gRPC хранит итоговый статус вызова. Дальше начинается черёд костылей: ручной REST-фасад, который дублирует логику и расходится с контрактом, или прокси Envoy с протоколом gRPC-Web, у которого свой список оговорок. В 2022 году компания Buf — та самая, что сделала тулчейн buf и Buf Schema Registry — открыла Connect: библиотеку, построенную ровно вокруг этой боли. Этот урок — connectrpc обзор в одном месте: сначала разберём, почему gRPC и браузер несовместимы, затем посмотрим на решение — один обработчик на net/http, который понимает Connect, gRPC и gRPC-Web одновременно, — а в конце честно обсудим, где Connect уместен, а где он лишний.

Что такое Connect RPC#

Connect — «slim library for building browser and gRPC-compatible HTTP APIs», как формулируют авторы: тонкая библиотека для построения браузерных и gRPC-совместимых HTTP API. Рецепт работы прежний, proto-first: пишете короткую Protocol Buffers-схему, реализуете логику приложения, а библиотека генерирует код, который делает сериализацию, маршрутизацию, компрессию и согласование форматов, плюс типизированный клиент. Для Go это модуль connectrpc.com/connect, официально рекомендуемый конструктор API поверх protobuf. Проект открыт в 2022 году под лицензией Apache 2.0 и развивается компанией Buf; кроме Go, есть реализации для TypeScript (пакет для браузера и Node), Kotlin, Swift, Dart и Python — экосистема уже мультиязычная, а не «Go-only». Ключевая идея всего фреймворка умещается в одну фразу из документации: handlers и clients поддерживают три протокола — gRPC, gRPC-Web и собственный протокол Connect. Схема одна, сервер один, порты и клиенты — по вкусу. В этом курсе мы изучим полный путь: установку с buf, первый сервис, протоколы, ошибки, interceptors, стриминг и браузерных клиентов.

Почему gRPC не работает в браузере#

Чтобы понять ценность Connect, нужно на минуту спуститься на уровень HTTP. gRPC ездит строго по HTTP/2 и активно использует его трейлеры — заголовки, которые сервер отправляет после тела ответа. Это не деталь реализации, а ядро дизайна: итоговый статус вызова (grpc-status) и сообщение об ошибке gRPC кладёт именно в трейлеры. У обычных HTTP-серверов статус живёт в первой строке ответа и известен до тела, у gRPC — в самом конце, потому что часть ошибок выявляется только когда ответ уже частично отослан. Теперь браузер. JavaScript-код не работает с HTTP напрямую: он говорит через Fetch API, а тот не отдаёт коду ни трейлеры, ни сырые HTTP/2-кадры. Сотрудники команды Connect в FAQ формулируют это жёстко и точно: многие HTTP-реализации, включая браузеры, трейлеры до сих пор не поддерживают — и потому никакой код в браузере не может говорить по протоколу gRPC. Заметьте, картина получается дурацкая: ответ физически пришёл, тело даже распарсилось, а фронтенд не может узнать, чем закончился вызов — успехом или ошибкой. Для API это не баг, а полный отказ: система, в которой нельзя узнать исход запроса, не годится.

gRPC-Web: обходной путь с прокси#

Первая попытка лечить это — протокол gRPC-Web, придуманный самой gRPC-командой. Идея простая до безобразия: вместо трейлеров закодировать служебную информацию в конец тела ответа. Трейлеров нет — браузеру они и не нужны. Но у gRPC-Web есть две оговорки, которые вы вспоминаете в самый неподходящий момент. Первая: чистые gRPC-серверы (grpc-go, grpc-java) по умолчанию gRPC-Web не говорят, поэтому между браузером и сервисом ставят прокси — обычно Envoy, — который переводит gRPC-Web в gRPC и обратно. Лишний hop, лишний деплой, лишняя точка, где что-то пойдёт не так. Вторая оговорка: gRPC-Web поддерживает только бинарный формат protobuf, поэтому в devtools браузера вы видите кашу из байтов вместо читаемого JSON — отлаживать неудобно. Со стримингом тоже не без клиники: серверный стриминг из браузера работает, а вот клиентский и двунаправленный — с серьёзными ограничениями, о которых честно предупреждают документации. Подытожу по-своему: gRPC-Web — это полумера, которая решает проблему трейлеров, но платит прокси и удобством разработки. Именно от этой полумеры Connect и отталкивается.

gRPC, gRPC-Web и Connect: сравнение#

КритерийgRPCgRPC-WebConnect
Транспорттолько HTTP/2HTTP/1.1 и HTTP/2HTTP/1.1 и HTTP/2 (bidi — только HTTP/2)
Трейлерыобязательны: grpc-status живёт тамнет, закодированы в телоне используются вовсе
Браузер напрямуюнетда, если сервер умеет gRPC-Webда, JSON из коробки
Прокси для чистого gRPC-серверане нуженнужен, обычно Envoyне нужен: handler сам понимает gRPC
Отладка curlпочти нет, нужен grpcurlтрудно: только бинарный форматда: JSON, читаемые статусы, живые пути
GET и HTTP-кешнетнетда, для методов без побочных эффектов

Почему браузерный JavaScript не может вызвать обычный gRPC-сервер напрямую?

Решение Connect: один обработчик на net/http#

Вот здесь и появляется обещание фреймворка: один сгенерированный обработчик обслуживает все три протокола — gRPC, gRPC-Web и Connect — различая их по заголовку Content-Type. Не три сервера на трёх портах и не прокси в придачу: один http.Handler в вашем mux. Под капотом Connect — только Protocol Buffers и стандартная библиотека Go: никакой собственной реализации HTTP, никакого нового API балансировки и разрешения имён. В README это звучит так: всё, что вы знаете о net/http, остаётся верным, и любой пакет, работающий с http.Server, http.Client или http.Handler, работает и с Connect. Практическое следствие сложно переоценить: ваши middleware, трейсинг, метрики, httptest-тесты, настройки таймаутов и graceful shutdown — всё стандартное — подключаются к Connect-сервису без адаптеров. Контракт по-прежнему живёт в .proto-файле, код генерируется компилятором, типы проверяются на сборке — вся gRPC-эргономика сохранена, сменился только транспортный слой. Именно поэтому переезд с gRPC на Connect часто оказывается меньшей операцией, чем кажется по названию курса.

Контракт: тот же protobuf, что и в gRPC

GO
syntax = "proto3";

package notes.v1;

option go_package = "example.com/notes/gen/notes/v1;notesv1";

message Note {
  string id = 1;
  string title = 2;
  string body = 3;
}

message GetNoteRequest {
  string id = 1;
}

message CreateNoteRequest {
  string title = 1;
  string body = 2;
}

service NoteService {
  rpc GetNote (GetNoteRequest) returns (Note);
  rpc CreateNote (CreateNoteRequest) returns (Note);
}

Анатомия знакома каждому, кто видел gRPC: syntax, package, go_package, messages с пронумерованными полями, service с rpc-методами. Никаких connect-специфичных аннотаций, никаких новых ключевых слов. Этот файл — обычный protobuf: его можно скормить protoc-gen-go и генератору gRPC, а можно — плагину Connect. Схема не знает, какой фреймворк её переведёт в код.

Тот же protobuf, другой транспорт-слой#

Проще всего понять Connect через сравнение с тем, что вы уже знаете. Если вы проходили курс по gRPC, вы собирали сервер так: net.Listen открывает TCP-порт, grpc.NewServer создаёт сервер, RegisterOrderServiceServer привязывает реализацию к сервису, Serve блокируется в цикле приёма — весь этот разбор в уроке про первый gRPC-сервер. В Connect эти слои меняются: вместо специализированного транспорта — обычный http.Handler, который монтируется в стандартный ServeMux и слушается вашим http.Server. Что при этом остаётся нетронутым: сами .proto-файлы, плагин protoc-gen-go (message-типы ни при чём — это про сериализацию), бинарный формат protobuf на проводе, модель кодов ошибок gRPC и весь словарь видов вызовов. Стриминг тоже на месте: если в курсе gRPC вы писали серверный стриминг WatchOrders — один запрос, поток событий — то в Connect то же самое выражается тем же словом stream в схеме; соответствующий разбор — в уроке про серверный стриминг курса gRPC. Формула короткая: тот же protobuf, другой транспорт-слой. Логика приложения, если она не вросла в grpc.Server, переезжает почти без изменений.

Сервер: обычный net/http, никаких grpc.NewServer

GO
package main

import (
        "context"
        "log"
        "net/http"

        "connectrpc.com/connect"
        notesv1 "example.com/notes/gen/notes/v1"
        notesv1connect "example.com/notes/gen/notes/v1/notesv1connect"
)

// Реализация сервиса: та же структура, что и у gRPC-сервера.
type notesServer struct {
        // Встроенный тип отвечает за совместимость при росте схемы.
        notesv1connect.UnimplementedNoteServiceHandler
}

func (s *notesServer) GetNote(ctx context.Context, req *notesv1.GetNoteRequest) (*notesv1.Note, error) {
        // Здесь будет поиск в базе, пока возвращаем заглушку.
        return &notesv1.Note{Id: req.Id, Title: "Первая заметка"}, nil
}

func main() {
        mux := http.NewServeMux()
        // Конструктор возвращает путь ("/notes.v1.NoteService/")
        // и http.Handler, который понимает все три протокола.
        path, handler := notesv1connect.NewNoteServiceHandler(&notesServer{})
        mux.Handle(path, handler)

        log.Fatal(http.ListenAndServe("localhost:8080", mux))
}

Смотрите на main: http.NewServeMux, монтирование handler-а, ListenAndServe — это обычный net/http, который вы пишете с первого дня в Go. grpc.NewServer и net.Listen отсюда исчезли, их заменил стандартный mux. Единственная connect-специфика — импорт connectrpc.com/connect и сгенерированный пакет notesv1connect с суффиксом connect: его создаёт плагин protoc-gen-connect-go, о нём ниже. Полный разбор этого файла с клиентом — в третьем уроке курса, пока важно запомнить форму: сгенерированный handler монтируется как обычный HTTP-роут.

Браузерный клиент вызвал GetNote с Content-Type: application/json. Что вернёт сервер?

Прочитайте код и выберите, что он выведет:

func (s *notesServer) GetNote(ctx context.Context, req *notesv1.GetNoteRequest) (*notesv1.Note, error) {
        // Заметки с таким id нет — возвращаем не ошибку Go, а код RPC.
        return nil, connect.NewError(connect.CodeNotFound, errors.New("заметка не найдена"))
}

Три протокола одним обработчиком#

Как же один handler различает трёх клиентов? По Content-Type, и правило выбора простое. Заголовки application/proto или application/json — это собственный протокол Connect: тело запроса и ответа без обёрток, осмысленные HTTP-статусы. Заголовок application/grpc (+ суффикс кодека) — классический gRPC: он приезжает на HTTP/2, и handler обслуживает его по спецификации gRPC, включая trailers и стриминг. Заголовок application/grpc-web+proto — gRPC-Web, тот самый протокол для браузеров с закодированными в тело трейлерами; Connect подаёт его нативно, без Envoy. Последний пункт стоит проговорить отдельно, потому что это прямое сравнение из FAQ проекта: серверы, построенные на Connect-Go, понимают gRPC-Web сами, а большинство реализаций gRPC от Google — нет, им нужен прокси. Практический итог для команды: старые gRPC-клиенты продолжают работать без перекомпиляции схем, фронтенд ходит JSON-ом из браузера, новый Go-клиент выбирает Connect-протокол — и всё это на одном порту, в одном деплое, под одним балансировщиком. Health-проверки и server reflection в Go-версии доступны отдельными пакетами, они тоже совместимы с gRPC.

Вызов curl: никакого специального клиента

GO
curl \
    --header "Content-Type: application/json" \
    --data '{"sentence": "I feel happy."}' \
    https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say

Это официальный живой демо-сервер проекта, можете выполнить команду прямо сейчас — ответ вернётся обычным JSON с HTTP 200. Путь собирается из схемы: /package.Service/Method, то есть /connectrpc.eliza.v1.ElizaService/Say. Свои сервисы дёргаются точно так же — на локальном сервере замените адрес на localhost. Для сравнения: чистый gRPC так не умеет, для ручных вызовов там нужен grpcurl и включённый reflection. Латинская буква в URL, русские комментарии в коде — смешение нормально: контракты интернациональны.

На ваш Connect-сервер пришёл запрос с Content-Type: application/grpc от старого gRPC-клиента. Что произойдёт?

Connect-протокол: HTTP-статусы вместо трейлеров#

Собственный протокол Connect задуман по трём правилам, которые авторы выписали в спецификации: быть читаемым обычными HTTP-инструментами, оставаться концептуально близким к gRPC и зависеть только от широко поддерживаемых фич HTTP. Для unary-вызовов результат похож на урезанный REST: POST на путь, выведенный из схемы (/notes.v1.NoteService/GetNote), тело — валидный protobuf или JSON без бинарной обёртки, а HTTP-статус ответа несёт смысл. Для методов без побочных эффектов схему помечают опцией idempotency_level = NO_SIDE_EFFECTS — и клиент отправляет обычный GET, который кешируется стандартной HTTP-инфраструктурой; gRPC такого подарка не делает. Стриминговые вызовы устроены чуть сложнее: Content-Type меняется на application/connect+json или application/connect+proto, каждое сообщение оборачивается несколькими байтами фрейминга, HTTP-статус всегда 200, а ошибка приходит в конце тела — по механике это ближе к gRPC-Web, только трейлеров нет и на HTTP/1.1 всё живёт. Исключение одно: двунаправленный стриминг требует HTTP/2, это ограничение протокола, а не библиотеки.

Коды ошибок RPC и их HTTP-статусы#

КодHTTP-статусТиповой смысл
invalid_argument400 Bad Requestзапрос некорректен независимо от состояния системы
unauthenticated401 Unauthorizedнет валидных учётных данных
permission_denied403 Forbiddenданные есть, доступа нет
not_found404 Not Foundзапрошенный ресурс не существует
already_exists409 Conflictсоздание ресурса, который уже есть
resource_exhausted429 Too Many Requestsисчерпан лимит или квота
unavailable503 Service Unavailableвременная недоступность, стоит ретраить
unimplemented501 Not Implementedметод не реализован или не включён
deadline_exceeded504 Gateway Timeoutдедлайн истёк до завершения вызова

К Connect-серверу подключили чистый grpc-go клиент (тот же .proto). Чем закончится вызов GetNote?

Прочитайте код и выберите, что он выведет:

// Клиент сгенерирован grpc-go по той же схеме notes.v1,
// без изменения .proto и без настроек прокси.
conn, err := grpc.NewClient("localhost:8080",
        grpc.WithTransportCredentials(insecure.NewCredentials()))
// ...
client := pb.NewNoteServiceClient(conn)
note, err := client.GetNote(ctx, &pb.GetNoteRequest{Id: "1"})

protoc-gen-connect-go и buf: proto-first кодогенерация#

Connect сохраняет главную дисциплину gRPC — proto-first: первична схема, код не пишется руками. Генерация двухступенчатая. Первый плагин — знакомый protoc-gen-go: он создаёт notes.pb.go с message-типами, и это ровно тот же файл, что и в gRPC-проекте. Второй — protoc-gen-connect-go: он создаёт notes.connect.go, где лежат интерфейс NoteServiceHandler с методом Unimplemented-заглушкой, конструктор NewNoteServiceHandler и типизированный клиент NewNoteServiceClient. Имя пакета получает суффикс connect — notesv1connect, — и по нему легко ориентироваться в импортах. Порядок установки такой: go get connectrpc.com/connect в проект, два плагина локально — и protoc с двумя --_out флагами. Но существует и современный путь: buf от компании-автора Connect заменяет хороводы protoc-флагов одним файлом buf.gen.yaml, добавляет линт схем, проверку breaking changes и реестр схем BSR. Это отдельная история, которой мы займёмся в следующем уроке про установку — там соберём notes.v1 из этого урока в рабочий код.

Восстановите ядро Connect-сервера: создание mux, монтирование сгенерированного handler-а и запуск стандартного HTTP-сервера.

func main() { mux := http.() path, handler := notesv1connect.(&notesServer{}) mux.Handle(path, handler) log.Fatal(http.("localhost:8080", mux)) }

Какой плагин генерирует файл notes.connect.go с handler-ами и клиентом?

Соберите по порядку путь от .proto-схемы до первого запроса curl к Connect-сервису.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Где Connect уместен, а где лишний#

Мой расклад по сценариям. Первый и самый убедительный: у вас Go-бэкенд и фронтенд в браузере — Connect убирает весь слой костылей: ни REST-фасада, который отстаёт от контракта, ни Envoy, ни второй схемы. Второй: существующий gRPC-продукт, у которого появился браузерный клиент — переезд наименьшей крови, потому что gRPC-протокол поддерживается handler-ом и старые клиенты даже не заметят смены. Третий: публичные API, где хочется типизацию protobuf, но и curl-отладку жалко — JSON по умолчанию закрывает обе потребности. Четвёртый — новый проект с TypeScript-клиентом: для меня Connect стал выбором по умолчанию, а не экспериментом. Когда он лишний? Если у вас чисто внутренние gRPC-системы, где всё уже работает: Envoy стоит, браузера нет, миграция ради миграции не окупается. Если главное требование — REST-совместимость для чужих интеграций, честнее взять REST или grpc-gateway, чем протокол, который лишь похож на REST. И если команда на два месяца, а сервис один — возможно, хватит и обычного JSON API. Connect силён там, где сходятся protobuf, браузер и желание жить на net/http.

Карточки: словарь Connect

Термин

Connect

нажмите, чтобы перевернуть
Определение

Тонкая библиотека от Buf для gRPC-совместимых HTTP API: proto-first, кодогенерация, handler и клиент говорят на трёх протоколах — Connect, gRPC, gRPC-Web. Открыта в 2022, Apache 2.0.

1 / 6

Что дальше по курсу#

Следующий урок — «Установка и настройка» (02-ustanovka): go get connectrpc.com/connect, плагины protoc-gen-go и protoc-gen-connect-go, первый buf.gen.yaml. Схему notes.v1 из этого урока мы доведём до генерируемого кода, и дальше по курсу она будет расти вместе с нами: третий урок соберёт первый Connect-сервис целиком, четвёртый разберёт протоколы на проводе, шестой — ошибки и connect.NewError, седьмой — interceptors, девятый — браузерных клиентов на TypeScript. Программа всех двадцати пяти уроков, от установки до итогового проекта, живёт на странице курса. Если protobuf для вас в новинку — начните с курса по gRPC: там протокол сериализации разобран глубже, чем это нужно для Connect.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • Connect от Buf — библиотека для gRPC-совместимых HTTP API: та же .proto-схема и protobuf, но транспорт — обычный net/http.
  • Причина появления: браузер не умеет gRPC — Fetch API не отдаёт трейлеры, где живёт итоговый статус grpc-status.
  • gRPC-Web решает проблему трейлеров, но требует прокси (Envoy) и оставляет только бинарный формат.
  • Один сгенерированный handler на net/http обслуживает три протокола: Connect, gRPC, gRPC-Web — по Content-Type.
  • Connect-протокол: POST/GET по пути /package.Service/Method, JSON или protobuf, осмысленные HTTP-статусы, not_found → 404.
  • Стриминг поддержан; из браузера полноценно работает серверный, клиентский и bidi ограничены Fetch API.
  • Миграция с gRPC мягкая: старые клиенты работают, схема не меняется, меняется плагин генерации.

Домашнее задание

Возьмите любой сервис из курса по gRPC и выпишите в две колонки: что переедет в Connect без изменений (схема, message-типы, коды ошибок, стриминг в схеме) и что придётся переделать (плагин генерации, регистрация сервера, транспорт). Затем выполните curl-команду из урока на живой демо-странице connectrpc.com и убедитесь, что RPC бывает читаемым без специальных клиентов.

Личные заметкиПусто
Хранятся только в вашем браузере (Connect · Введение в Connect RPC)

Вопросы и ответы по уроку

Что такое Connect RPC простыми словами?

Библиотека RPC от Buf: контракт в protobuf, сгенерированные типизированные клиенты, а на сервере — обычный HTTP-handler на net/http, который понимает gRPC, gRPC-Web и собственный протокол Connect одновременно.

Connect заменяет gRPC?

Не заменяет, а обнимает: та же схема, те же коды ошибок и стриминг, плюс протокол, который работает в браузере и читается curl-ом. Существующие gRPC-клиенты продолжают работать без изменений.

Чем Connect отличается от gRPC-Web?

gRPC-Web — только браузерный протокол с бинарным форматом, которому чистому gRPC-серверу нужен прокси. Connect — и протокол, и библиотека: без прокси, с JSON по умолчанию, и при этом полноценная совместимость с gRPC.

Термины урока

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

Сравните с альтернативами

Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.

Следующая ступень учебного пути

Fyne25 уроков~13 чпрактика25 уроков уже доступны

Fyne: десктопные и мобильные GUI на Go

Курс Fyne: окна, виджеты, контейнеры, canvas, темы, binding, сборка под мобильные и десктоп. 25 уроков и итоговый менеджер задач.

  • Создавать окна и интерфейсы из виджетов
  • Управлять компоновкой: Box, Grid, Form
  • Рисовать примитивами canvas и строить свои виджеты
  • Связывать данные через binding