Содержание урока · 13 разделов
- Хендлер — это http.Handler: что из этого следует
- ServeMux 1.22 и когда приходит chi
- Middleware net/http: паттерн и порядок
- Граница ответственности: middleware против интерсепторов
- chi, Echo, Gin: нативная посадка
- Префиксы и несколько сервисов
- h2c: HTTP/2 без TLS внутри контура
- Health-checks и graceful shutdown
- Где решать задачу: middleware или интерсептор
- Что дальше по курсу
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Самый практичный факт про Connect вы уже знаете с третьего урока, но сейчас он раскроется целиком: NewNoteServiceHandler возвращает строку пути и handler типа http.Handler. Не особый интерфейс, не магический тип — ровно тот http.Handler с методом ServeHTTP, на котором стоит весь стандартный Go. Из этого следует всё содержание урока: ваш RPC-сервис монтируется на любой mux — стандартный ServeMux, chi, echo — рядом с обычными HTTP-роутами; оборачивается любыми middleware net/http; живёт за теми же прокси и балансировщиками; выключается тем же graceful shutdown-ом. Интеграция connect не требует ни одного пакета-адаптера, потому что адаптер уже написан: сам Connect построен на net/http — это его принципиальное отличие от gRPC, который принёс свой транспорт. Седьмой урок обещал, что мы обернём хендлер в цепочки chi и Echo — обещание исполняется в этом уроке. В тринадцатом FAQ мы зафиксировали границу «про RPC — в интерсептор, про HTTP — в middleware» — теперь построим ту цепочку руками. План: монтаж и соседи по mux, middleware-паттерн и его порядок, chi/echo/gin и честный разговор про fiber, StripPrefix и префиксы, h2c для plaintext HTTP/2, health-checks и корректное выключение.
Хендлер — это http.Handler: что из этого следует#
Разберём сам факт до конца. Первый аргумент возврата NewNoteServiceHandler — путь /notes.v1.NoteService/ — строка, под которой зарегистрированы все процедуры сервиса; второй — http.Handler, который принимает каждый вызов, разбирает протокол (Connect, gRPC или gRPC-Web — механика четвёртого урока) и диспатчит на ваш implementation. Связка connect net http буквальна до конца: так как это http.Handler, к нему применимо всё, что вы знаете о net/http, и это не метафора, а буквальная замена компонентов: http.Server с TLS-конфигом из тринадцатого урока обслуживает RPC-вызовы тем же кодом, что и статику; middleware из любого учебника по Go оборачивают его без единой правки; httptest.NewServer из шестнадцатого урока поднимает его для тестов той же строкой, что и обычный роутер. Обратная сторона той же медали: ограничения net/http тоже ваши — таймауты сервера, лимиты заголовков, поведение при паниках, модель горутин на соединение. В gRPC-мире интеграция с обычным HTTP-миром — проект: grpc-go живёт в своей горутине с своим транспортом, и совместный mux требует cmux или отдельных портов. В connect-мире вопрос «как поставить RPC рядом с health-check и метриками» — это одна пара mux.Handle. Именно за это свойство Connect ценят в командах со смешанной инфраструктурой: RPC-сервис не требует изменений в платформе, которая уже гоняет HTTP.
Базовый монтаж: RPC, healthz и metrics на одном mux
GOmux := http.NewServeMux()
// соседи по mux — обычные HTTP-роуты (паттерны Go 1.22: метод + путь)
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
mux.Handle("GET /metrics", promhttp.Handler()) // из семнадцатого урока
// RPC-хендлер: строка пути + http.Handler
path, handler := notesv1connect.NewNoteServiceHandler(
¬esServer{},
connect.WithInterceptors(&metricsInterceptor{}, &authInterceptor{}),
)
mux.Handle(path, handler)
log.Fatal(http.ListenAndServe(":8080", mux))Вся сборка — стандартный net/http: два обычных роута и один RPC-хендлер на одном mux. Паттерны Go 1.22 ограничивают методы соседей; путь RPC-сервиса приходит из генератора и покрывает все его процедуры. Интерсепторы из семнадцатого и тринадцатого уроков живут внутри хендлера, middleware — снаружи: две линии обороны, о которых ниже.
Что возвращает NewNoteServiceHandler?
ServeMux 1.22 и когда приходит chi#
Стандартный http.ServeMux после Go 1.22 вырос из игрушки в рабочий инструмент, и для многих сервисов этого достаточно: паттерны с методом (GET /healthz), сегменты-подстановки (/users/{id} и r.PathValue("id")), завершающий слэш-суффикс {path...} для префиксов. Если ваш сервис — RPC плюс пара служебных роутов, депенденси не нужен: ServeMux лёгок, предсказуем и уже в рутине. chi приходит, когда нужен настоящий роутинг-стек: группы роутов с middleware на группу, вложенные маршруты, сотни роутов с общими префиксами, комбинаторика middleware по поддеревьям. Echo — когда команда уже живёт в его экосистеме: те же группы, свой контекст запроса, свои хелперы ошибок, обширная коллекция middleware. Важно понимать цену выбора: всё это — роутеры поверх net/http, они принимают http.Handler и отдают http.Handler, поэтому connect-хендлер садится в них нативно, без адаптеров — интеграция connect сводится к одному вызову в каждом фреймворке. Отдельный жанр — REST-соседи RPC: на том же mux удобно держать несколько публичных GET-роутов для простых клиентов; полноценный REST-шлюз над proto-схемой — тема двадцать первого урока, там же сравнение с grpc-gateway, а сейчас — только соседи по роутеру.
Middleware net/http: паттерн и порядок#
Middleware net/http — это функция вида func(http.Handler) http.Handler: принимает следующий обработчик, возвращает обёрнутый. Внутри — замыкание с вашей логикой до и после next.ServeHTTP: до — подготовка (таймер, контекст, лимиты), после — фиксация (лог, метрика), вокруг — защита (recover). Композиция — матрёшка: withLogging(withRecovery(mux)) означает, что logging снаружи, recovery внутри, mux в центре; запрос проходит logging → recovery → mux, ответ — обратно. Порядок — это контракт, как у WithInterceptors из седьмого урока: внешний слой видит весь жизненный цикл запроса, включая то, что внутренние слои сделали с ответом. Классическая пара — recovery внутри logging: тогда лог-строка пишется и для паниковавших запросов, после того как recovery их погасил и вернул 500. Поменяйте порядок — и запрос с паникой останется без лог-строки: внешний recovery погасит её раньше, чем logging увидит запрос. Дежурная коллекция middleware: logging с длительностью, recovery с честным 500, лимит тела http.MaxBytesReader, таймаут через http.TimeoutHandler (с оговоркой для стримов — ниже), заголовки безопасности. Всё это живёт на уровне HTTP: middleware не знает ни процедур, ни сообщений — он видит пути и статусы, и это нормальная точность для его задач.
Пара recovery + logging
GOfunc withRecovery(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
log.Printf("panic: %v", rec)
http.Error(w, "internal error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func withLogging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
handler := withLogging(withRecovery(mux))
log.Fatal(http.ListenAndServe(":8080", handler))Обёртка через http.HandlerFunc — канонический паттерн без зависимостей. Recovery снаружи mux-а защищает и RPC-хендлер, и соседей по роутеру; logging видит все запросы, включая /metrics и /healthz. Заметьте, что RPC-лог по-прежнему пишут интерсепторы из седьмого урока — с процедурой и кодом; middleware-лог дополняет картину путями и статусами HTTP.
Что окажется в логах при панике?
Прочитайте код и выберите, что он выведет:
handler := withLogging(withRecovery(mux))
// Хендлер GetNote запаниковал: panic("nil map write")Граница ответственности: middleware против интерсепторов#
Седьмой урок построил карту, тринадцатый закрепил её в FAQ — теперь она стала кодом, и стоит сформулировать правило одной строкой: то, что про HTTP, — в middleware; то, что про вызов RPC, — в интерсептор. В middleware: recovery (защита всего процесса HTTP, включая соседей по mux), логирование на уровне путей и статусов, редиректы, заголовки ответа, CORS для браузерной границы из девятого урока, лимиты соединений. В интерсепторах: аутентификация и авторизация с req.Spec().Procedure, логирование с процедурой и кодом, метрики вызовов из семнадцатого урока, ретраи и трассировка на клиенте. Два критерия помогают решать пограничные случаи. Критерий видимости: если логике нужна процедура или сообщение — интерсептор, middleware их не видит. Критерий стороны: интерсепторы работают и на клиенте (клиентская луковица из седьмого урока), middleware — только на сервере; логика, нужная обоим концам, живёт в интерсепторе. Пограничный жанр — таймауты: per-call дедлайн ставит клиент контекстом из пятого урока, сервер его уважает автоматически; общий таймаут HTTP — http.TimeoutHandler, но для стримов из восьмого урока он вреден: длинный bidi-поток убьётся по таймеру даже при активном обмене, поэтому глобальный таймаут либо ставят только на unary-подобные пути, либо держат дедлайны на уровне контекста вызова.
Паника в RPC-хендлере без middleware recovery — что делает net/http по умолчанию?
chi, Echo, Gin: нативная посадка#
Посадка в популярные роутеры — по одному вызову, потому что все они живут на http.Handler. chi: r.Handle(path, handler) или r.Mount(prefix, handler) для поддерева; middleware chi — через r.Use(withLogging, withRecovery) — и connect-хендлер оказывается внутри всей цепочки автоматически. Echo: e.Any(path, echo.WrapHandler(handler)) — обёртка из коробки; с middleware echo.WrapMiddleware если хотите вклинить echo-хендлер в общий слой. Gin: r.Any(path, gin.WrapH(handler)) — симметричная обёртка. Во всех трёх случаях путь RPC-хендлера берётся у генератора, как и со стандартным mux, и клиенты не замечают, на каком роутере живёт сервер. Честный разговор про fiber: он не построен на net/http — fiber стоит на fasthttp, другой реализации HTTP с другими интерфейсами; connect-хендлер в него не вставляется напрямую, нужен адаптер fasthttp-to-net/http с конвертацией контекста, а это трение, дублирование и риск в горячем пути. Практический вывод курса: connect-сервис держите на net/http-стеке (чистый ServeMux, chi, echo, gin — что угодно из http.Handler-мира), а fiber — для сервисов, где Connect не участвует. Ключевая фраза для поиска в доках — connect middleware: раздел интеграции показывает ровно эти обёртки для каждого фреймворка.
Монтаж на chi и Echo
GO// chi
r := chi.NewRouter()
r.Use(withLogging, withRecovery) // middleware на всё дерево
r.Get("/healthz", healthHandler)
r.Mount(notesPath, notesHandler) // notesPath и notesHandler от генератора
// Echo
e := echo.New()
e.Use(echoWrapMiddleware)
e.GET("/healthz", healthEcho)
e.Any(notesPath, echo.WrapHandler(notesHandler))Оба роутера принимают http.Handler без адаптеров: chi — через Mount с префиксом от генератора, Echo — через echo.WrapHandler. Проверяйте, что middleware зарегистрированы до монтирования хендлера, — иначе часть запросов пройдёт мимо цепочки. Клиентам всё равно, какой роутер внутри: путь процедуры тот же, протокол тот же.
На каком пути ответит GetNote?
Прочитайте код и выберите, что он выведет:
api := http.NewServeMux()
path, handler := notesv1connect.NewNoteServiceHandler(¬esServer{})
api.Handle(path, handler) // /notes.v1.NoteService/
root := http.NewServeMux()
root.Handle("/rpc/", http.StripPrefix("/rpc", api))
client := notesv1connect.NewNoteServiceClient(httpClient, "http://localhost:8080/rpc")Префиксы и несколько сервисов#
Реальный бэкенд — не один сервис, и mux справляется с этим без церемоний: NewNoteServiceHandler и, скажем, NewUserServiceHandler монтируются рядом, у каждого свой путь из имени пакета — /notes.v1.NoteService/ и /users.v1.UserService/ — маршрутизация автоматична, как и для версий из восемнадцатого урока, которые вы можете смонтировать тут же третьей и четвёртой парой. Префикс появляется, когда деплой-топология требует общего сегмента пути: API-шлюз снимает свой префикс, или все RPC живут под /rpc, или версия деплоя видна в пути. Инструмент — http.StripPrefix: внешний mux маршрутизирует по префиксу и срезает его перед внутренним mux, где хендлеры зарегистрированы под своими родными путями; критично, чтобы базовый URL клиентов содержал тот же префикс, иначе внешний mux не найдёт путь. Обратный вариант — mount без StripPrefix: если внутренний хендлер умеет жить с префиксом (chi Mount так делает сам), обёртка не нужна. Практический совет: один префикс на всю RPC-плоскость проще, чем разные, — тогда ingress-правила из двадцать второго урока будут однострочными, а дашборды метрик из семнадцатого будут читать procedure без сюрпризов. Помните также, что mux.ServeHTTP линейно перебирает паттерны — на сотнях роутов это не проблема, но измеряйте, если подозреваете маршрутизацию в профиле CPU.
h2c: HTTP/2 без TLS внутри контура#
Сценарий из тринадцатого урока: TLS терминируется на ingress, дальше к сервису едет plaintext HTTP. Тут ждёт подводный камень, который кусает всех, кто гоняет gRPC-жанры: grpc-клиент требует HTTP/2, а net/http по умолчанию говорит HTTP/2 только поверх TLS; plaintext-соединение сервер умеет только в HTTP/1.1 — и gRPC-вызовы за Terminating-TLS-балансировщиком падают с загадочными ошибками протокола. Connect-протокол из четвёртого урока неприхотлив — он работает и на HTTP/1.1, это осознанное свойство дизайна, — но если ваши жанры включают gRPC или gRPC-Web для старых клиентов, сервер должен уметь HTTP/2 без TLS. Ответ — h2c: пакет golang.org/x/net/http2/h2c, обёртка h2c.NewHandler(mux, &http2.Server{}) перед ListenAndServe. Она позволяет серверу принять HTTP/2-апгрейд или prior-knowledge соединение на plaintext-порту — и gRPC-жанры оживают за TLS-балансировщиком при одном условии: ingress обязан проксировать HTTP/2 внутрь, а не даунгрейдить до 1.1; это настройка ingress, и в двадцать втором уроке она займёт своё место в деплой-чек-листе. Запомните трио: TLS на границе, h2c внутри, HTTP/2-проксирование в ingress — стандартная топология gRPC-совместимых сервисов на Connect.
Сервер с h2c
GOimport (
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
mux := http.NewServeMux()
// ... healthz, metrics, notes handler ...
srv := &http.Server{
Addr: ":8080",
Handler: h2c.NewHandler(mux, &http2.Server{}),
}
log.Fatal(srv.ListenAndServe())Одна обёртка Handler — и plaintext-порт принимает и HTTP/1.1 (Connect-протокол), и HTTP/2 (gRPC-жанры за TLS-ingress). Проверка после деплоя: вызов grpcurl-ом и вызов curl-ом на один порт — оба должны работать; если gRPC падает, а Connect жив, смотрите, что ingress реально проксирует HTTP/2, а не даунгрейдит.
TLS терминируется на ingress, внутрь — plaintext. Что произойдёт с gRPC-клиентом без h2c на сервере?
Health-checks и graceful shutdown#
Последние два слоя интеграции — жизнь и смерть сервиса. Health-checks: на том же mux живут GET /healthz (жив ли процесс) и по желанию /readyz (готов ли принимать трафик) — обычные http.HandlerFunc без единой connect-строки; оркестратор пингует их, а RPC-плоскость остаётся чистой от служебных маршрутов. Готовность честнее всего связывать с реальными зависимостями: проверка коннекта к базе с таймаутом в полсекунды — но держите её лёгкой, readiness пингуют часто. Выключение: graceful shutdown — это гарантия, что деплой не рвёт активные вызовы. Схема: http.Server с h2c-обёрткой, сигнал SIGTERM ловится signal.NotifyContext, затем srv.Shutdown(ctx) — сервер перестаёт принимать новые соединения и ждёт активные до дедлайна контекста; для RPC-мира важна деталь: unary-вызовы успевают завершиться за типичный grace-период 15-30 секунд, а долгие bidi-стримы не закончатся сами — их гасит дедлайн, и клиент из пятого урока обязан переживать обрыв ретраем или переподключением; держите grace-период больше p99 длительности unary из семнадцатого урока — вот где метрики встречаются с деплоем. После Shutdown закрываются ресурсы: базы, кэши. Кейсы, когда Shutdown не спасает: незакрытые стримы без дедлайнов — ставьте таймаут на контекст стрима; клиенты без ретраев — деплой всё равно переживут благодаря балансировщику, выводящему поду из ротации до сигнала, — это связка readiness и деплоя, её разбор в двадцать втором уроке.
Graceful shutdown скелет
GOsrv := &http.Server{
Addr: ":8080",
Handler: h2c.NewHandler(withLogging(withRecovery(mux)), &http2.Server{}),
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
go func() {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("listen: %v", err)
}
}()
<-ctx.Done() // SIGTERM от оркестратора
shutdownCtx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Printf("shutdown: %v", err)
}
db.Close()Полный жизненный цикл: сервер в горутине, блокировка до сигнала, Shutdown с дедлайном 20 секунд — больше p99 unary из метрик, — затем закрытие зависимостей. Активные вызовы доезжают, новые отклоняются; долгие стримы гасятся дедлайном, и клиент обязан переживать обрыв. Оркестратор подождёт readiness-пробу соседней версии, прежде чем слать SIGTERM следующей.
Где решать задачу: middleware или интерсептор#
| Задача | Место | Инструмент | Почему там |
|---|---|---|---|
| recovery после паник | middleware | обёртка с recover вокруг mux | защищает все роуты и превращает панику в честный 500 |
| лог HTTP-запросов | middleware | logging-обёртка пути и статуса | видит и RPC, и /metrics, и /healthz одним слоем |
| аутентификация вызова | интерсептор | WrapUnary + conn.RequestHeader | нужны процедура и коды 401/403 из тринадцатого урока |
| метрики вызовов | интерсептор | интерсептор из семнадцатого урока | метки procedure и code недоступны middleware |
| лимит сообщения | опция хендлера | WithReadMaxBytes | цена считается на уровне сообщения, не HTTP-тела |
| дедлайн вызова | контекст | с клиента из пятого урока | per-call семантика; http.TimeoutHandler режет стримы |
Соберите middleware и монтаж
Восстановите сигнатуру middleware и регистрацию хендлера.
Путь запроса через полный стек
Расставьте события одного вызова CreateNote от приёма до ответа.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
fiber стоит на fasthttp — другой реализации HTTP с несовместимыми интерфейсами: connect-хендлер в него не вставляется без адаптера с конвертацией контекста; для Connect-сервисов выбирайте стек из http.Handler-мира. Вторая ловушка — http.TimeoutHandler как общий таймаут: длинные bidi-стримы будут убиты таймером даже при активном обмене; дедлайны стримов держите в контексте вызова, глобальный таймаут — только на unary-подобных путях.
Не тяните роутер-фреймворк в сервис с RPC и двумя служебными роутами: паттерны стандартного mux (метод, подстановки, PathValue) покрывают большинство случаев, а меньше зависимостей — меньше сюрпризов при апгрейдах Go. chi добавляйте, когда появились группы с middleware на поддеревья; порядок обёрток фиксируйте комментарием-схемой, как учил седьмой урок для интерсепторов, — это тот же контракт.
Код урока — настоящий Go: middleware, монтаж и shutdown-скелет копируются в проект из третьего урока один в один и проверяются тестами из шестнадцатого. Браузерная песочница сайта сетевых вызовов не исполняет, поэтому исполняемых блоков в курсе нет — интерактивы работают на странице и дают XP, а сборку проверяйте curl-ом на healthz, metrics и пути процедуры.
Карточки: Connect и net/http
Что дальше по курсу#
Интеграция закрыта со всех сторон: хендлер стоит на общем mux с healthz и метриками, middleware и интерсепторы делят границу ответственности, h2c открывает gRPC-жанры за TLS-ingress, graceful shutdown выключает сервис без разрыва вызовов. Дальше по курсу — миграция с gRPC на Connect: пошаговый план переезда существующего сервиса, где сегодняшняя тема станет фундаментом — хендлеры двух миров встанут на один mux на время перехода. Потом — REST-шлюз и HTTP-совместимость, деплой с ingress и HTTP/2 на границе. Для повторения: первый монтаж и структура проекта — в третьем уроке, три протокола и их требования к HTTP — в четвёртом, TLS-топология — в тринадцатом, httptest для проверки всей сборки — в шестнадцатом. Программа всех двадцати пяти уроков — на странице курса.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Хендлер Connect — http.Handler: весь мир net/http работает без адаптеров; gRPC для совместного mux требует cmux или отдельного порта.
- ServeMux 1.22 с паттернами закрывает сервис с RPC и служебными роутами; chi и echo — когда нужны группы и middleware на поддеревья.
- Middleware — func(http.Handler) http.Handler; порядок обёрток — контракт: recovery внутри logging, чтобы паниковавшие запросы попали в лог.
- Граница: про HTTP — middleware (recovery, лог путей, CORS), про вызов — интерсептор (токен, метрики, коды); per-call дедлайны — в контексте.
- Паника без recovery: net/http гасит её на запрос, рвёт соединение и пишет стек; recovery-middleware даёт честный 500.
- h2c.NewHandler учит plaintext-порт HTTP/2 — gRPC-жанры за TLS-ingress живут; ingress обязан проксировать HTTP/2 внутрь.
- Graceful shutdown: Shutdown с grace-периодом больше p99 unary; долгие стримы гасит дедлайн, клиент переживает обрыв ретраем.
- fiber стоит на fasthttp — Connect туда не вставляется без адаптера; для connect-сервисов выбирайте http.Handler-стек.
Домашнее задание
Соберите notes-сервис как полноправного гражданина net/http-мира и попробуйте его на прочность деплоем. Шаг первый: общий mux с GET /healthz, GET /metrics из семнадцатого урока и notes-хендлером; оберните всё в withLogging(withRecovery(mux)) и поднимите через h2c.NewHandler на plaintext-порту. Шаг второй — проверка middleware: добавьте хендлер-паниковщик на тестовый путь и убедитесь curl-ом, что приедет 500, а в логах будут и panic-строка, и лог-строка с длительностью; поменяйте порядок обёрток и зафиксируйте исчезновение лог-строки. Шаг третий — префикс: вынесите RPC под /rpc через StripPrefix, обновите базовый URL клиента из пятого урока и проверьте оба жанра — Connect по HTTP/1.1 и grpc-клиент по HTTP/2 (если собран grpcurl — и его). Шаг четвёртый — выключение: повесьте signal.NotifyContext и Shutdown с дедлайном, запустите долгий bidi-стрим и пошлите SIGTERM; убедитесь, что unary-вызовы доезжают, а стрим гасится дедлайном, и запишите в заметки свой grace-период против p99 из метрик. Шаг пятый: смонтируйте рядом второй сервис (users или версии из восемнадцатого урока) и убедитесь, что пути разъезжаются без конфигурации.
Личные заметкиПусто
Вопросы и ответы по уроку
Что ставить между middleware и интерсепторами, если задача подходит обоим?
Спросите: нужна ли логике процедура, код ошибки или сообщение? Если да — интерсептор, middleware этого не видит. Нужна ли логика на клиенте? Интерсептор работает и там, middleware — только на сервере. Правило тринадцатого урока остаётся: про RPC — в интерсептор, про HTTP — в middleware, а задачи типа лимитов решаются третьим способом — опциями хендлера (WithReadMaxBytes).
Как монтировать Connect-хендлер в существующее Echo/chi-приложение?
Echo: e.Any(path, echo.WrapHandler(handler)) — обёртка принимает http.Handler; chi: r.Mount(path, handler). Путь берите у генератора, как и для ServeMux. Два нюанса: регистрируйте middleware до монтирования, чтобы RPC попал под цепочку, и проверьте, что body-лимиты фреймворка не конфликтуют с WithReadMaxBytes — из двух лимитов сработает меньший.
Нужен ли h2c, если жанры только Connect-протокол?
Нет: Connect-протокол работает поверх HTTP/1.1 и HTTP/2 одинаково, обёртка не обязательна. h2c нужен, когда в хендлере живут gRPC или gRPC-Web клиенты (они требуют HTTP/2), а TLS терминируется раньше сервера. Если сомневаетесь — поставьте h2c: обёртка прозрачна для HTTP/1.1 и ничего не ломает, зато жанры не отвалятся при добавлении старых клиентов.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Сравните с альтернативами
Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.
Изучайте дальше
Следующая ступень учебного пути
Fyne: десктопные и мобильные GUI на Go
Курс Fyne: окна, виджеты, контейнеры, canvas, темы, binding, сборка под мобильные и десктоп. 25 уроков и итоговый менеджер задач.
- Создавать окна и интерфейсы из виджетов
- Управлять компоновкой: Box, Grid, Form
- Рисовать примитивами canvas и строить свои виджеты
- Связывать данные через binding