Основные концепции
Поняв перечисленные ниже концепции, вы быстро составите целостное представление об HTTPC.
Обзор ключевых компонентов
| Компонент | Ответственность | Ключевые моменты |
|---|---|---|
Client (интерфейс) | Выполнение запросов, управление пулом соединений и жизненным циклом | Создаётся New(cfg) / NewDefault(); 7 глагольных методов + Request + Download + Close |
Doer (интерфейс) | Минимальный интерфейс запроса | Единственный метод Request(ctx, method, url, opts...); для mock и пользовательских реализаций |
RequestOption (функции With*) | Формирование отдельного запроса | Функциональные опции; применяются в порядке передачи, любая ошибка немедленно прерывает запрос |
MiddlewareFunc / Handler | Middleware и конечный обработчик | Луковая модель; композиция через Chain(mw...) |
RequestMutator / ResponseMutator | Представления чтения/записи запроса/ответа внутри middleware | На фазе запроса читают/пишут запрос, на фазе ответа — ответ |
SessionManager | Хранилище состояния сессии | Потокобезопасен; единообразно управляет Cookie и общими заголовками |
DomainClienter (интерфейс) | Доменный клиент | Привязка base URL + встроенная сессия; относительные пути склеиваются автоматически |
Result | Обёртка ответа | Трёхчастная структура «запрос / ответ / метаданные»; nil-безопасные методы доступа; автоматическая сборка GC |
ClientError | Классификация ошибок сетевого уровня | Извлекается через errors.As; Code() / IsRetryable() / Attempts |
Связи между компонентами:
Функции пакета ──используют──▶ Client по умолчанию ◀──создаёт── New(cfg)
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Цепочка middleware Выполнение Download
(опционально) движком потоковое
Chain(mw...) проверки/повторы скачивание файлов
│ │
▼ ▼
RequestMutator Result (Request / Response / Meta)
DomainClienter = Client + base URL + SessionManager
(заголовки и Cookie сессии добавляются перед запросом, Set-Cookie записывается после ответа)Двухслойная архитектура API
HTTPC предлагает два равнозначных способа выполнения запросов, соответствующих отношению http.Get и http.Client в стандартной библиотеке net/http:
Функции пакета — нулевая конфигурация, внутри общий лениво инициализируемый клиент по умолчанию; подходят для скриптов и разовых запросов:
result, err := httpc.Get("https://api.example.com/data")Экземпляр Client — полный контроль над конфигурацией, пулом соединений и жизненным циклом; подходит для долго работающих сервисов:
client, err := httpc.NewDefault()
defer func() { _ = client.Close() }()
result, err := client.Get("https://api.example.com/data")Оба способа принимают одинаковые опции запроса (WithHeader, WithJSON…) и возвращают один и тот же тип *Result. Функции пакета — тонкая обёртка над экземпляром Client. Клиент по умолчанию можно заменить: SetDefaultClient(client) назначает пользовательский экземпляр по умолчанию (старый закрывается автоматически), CloseDefaultClient() закрывает и сбрасывает его — после закрытия следующий пакетный вызов автоматически пересоздаст клиент.
Что выбрать?
Разовые запросы или быстрый прототип → функции пакета. Продакшен-сервис, пользовательская конфигурация или управление пулом соединений → экземпляр Client.
Система конфигурации: Config и опции With*
HTTPC делит конфигурацию на два независимых слоя, исключая путаницу:
| Слой | Носитель | Область действия | Типичные поля |
|---|---|---|---|
| Конфигурация экземпляра | Структура Config | Весь жизненный цикл клиента | Таймауты, стратегия повторов, пул соединений, TLS |
| Опции запроса | Функции WithXxx() | Один запрос | WithHeader, WithJSON, WithTimeout |
Конфигурация экземпляра передаётся в New() через структуру Config — начните с DefaultConfig() и меняйте поля по необходимости:
cfg := httpc.DefaultConfig()
cfg.Timeouts.Request = 60 * time.Second
cfg.Retry.MaxRetries = 5
client, err := httpc.New(cfg)Опции запроса передаются при каждом вызове, дополняя или переопределяя значения уровня экземпляра:
result, err := client.Get(url,
httpc.WithHeader("Authorization", "Bearer "+token),
httpc.WithTimeout(30*time.Second),
)Можно также использовать пресеты (SecureConfig(), PerformanceConfig() и др.) как отправную точку — см. API конфигурации.
Конфигурация всей библиотеки следует единому соглашению: главная Config и SessionConfig передаются по значению (обязательны); конфигурации middleware передаются по указателю, где nil означает значения по умолчанию; DownloadConfig передаётся по указателю и требует установленного FilePath. У каждого XxxConfig есть конструктор DefaultXxxConfig() — начать со значений по умолчанию и менять нужные поля, это единый стиль конфигурирования HTTPC.
Жизненный цикл запроса
Каждый запрос проходит следующие этапы:
Применение опций → Цепочка middleware (если есть) → Выполнение движком → Повторы (если нужно) → Возврат Result
↑ ↑
With* функции Пул соединений / TLS / прокси / SSRF-проверкиДетали этапов:
- Применение опций — функции
With*задают заголовки, тело, таймаут и т.д.; выполняются в порядке передачи, любая ошибка опции (например, заголовок не прошёл CRLF-проверку) немедленно завершает запрос ошибкой. - Цепочка middleware — включается при заданном
Config.Middleware.Middlewares; запрос проходит middleware в порядке регистрации, конечный Handler передаёт (возможно изменённый) запрос движку. Если middleware не настроены, запрос идёт прямо в движок — нулевые накладные расходы. - Выполнение движком — валидация URL/заголовков (включена по умолчанию) → SSRF-проверка соединения (по умолчанию блокирует приватные IP) → DNS-разрешение (опционально DoH) → получение соединения из пула (при нехватке — новое, в пределах
MaxConnsPerHost) → TLS-рукопожатие (политика версий, опциональное закрепление сертификатов) → отправка запроса → чтение ответа (проверка лимитов размера тела и распаковки). - Повторы — повторимые условия: таймаут, транспортные ошибки, большинство транзитных сетевых ошибок, а также коды 408/429/500/502/503/504. Откат вычисляется как
Delay × BackoffFactor^nс джиттером, одно ожидание не превышаетMaxRetryDelay; при наличии заголовкаRetry-Afterон имеет приоритет (максимум 60s). Общая длительность ограниченаTimeouts.RequestилиWithTimeout— бюджет таймаута общий для всех повторов, каждый раунд его не сбрасывает. - Result — содержит данные ответа, метаданные запроса и статистику повторов; внутренние объекты движка пулируются, но это прозрачно для вызывающего,
Resultсобирается GC автоматически, ручное освобождение не нужно.
Два финала при исчерпании повторов:
- Исчерпание из-за сетевой ошибки → возвращается
error(ClientError.Attemptsфиксирует число попыток); - Исчерпание повторяемого статуса (например, 503) → возвращается последний ответ (
result.StatusCode() == 503,Meta.Attemptsфиксирует общее число), вызывающий обрабатывает код состояния сам.
Модель middleware
Middleware — функции вида func(Handler) Handler (MiddlewareFunc), а Handler — сигнатура функции, реально обрабатывающей запрос:
type Handler func(ctx context.Context, req RequestMutator) (ResponseMutator, error)
type MiddlewareFunc func(Handler) HandlerMiddleware регистрируются в Config.Middleware.Middlewares и комбинируются Chain(middlewares...) в луковую модель: оборачивают в порядке регистрации — первое middleware является самым внешним слоем; фаза запроса выполняется по порядку, фаза ответа — в обратном.
Каркас пользовательского middleware:
func TimingMiddleware(report func(d time.Duration)) httpc.MiddlewareFunc {
return func(next httpc.Handler) httpc.Handler {
return func(ctx context.Context, req httpc.RequestMutator) (httpc.ResponseMutator, error) {
start := time.Now()
resp, err := next(ctx, req) // Вызов внутреннего слоя (следующее middleware или движок)
report(time.Since(start)) // Логика фазы ответа (выполняется в обратном порядке)
return resp, err
}
}
}Обзор встроенных middleware:
| Middleware | Ответственность | Поведение при конфигурации nil |
|---|---|---|
LoggingMiddleware | Вывод сводки запроса/ответа (URL автоматически маскируется) | Логирование выключено (no-op) |
RecoveryMiddleware | Перехват паник внутри цепи с преобразованием в error | — |
RequestIDMiddleware | Внедрение X-Request-ID (генерация crypto/rand) | Имя заголовка и безопасный генератор по умолчанию |
TimeoutMiddleware | Таймаут на уровне middleware (срабатывает раньше встроенного таймаута клиента) | Таймаут выключен (сквозной) |
MetricsMiddleware | Колбэк на каждый запрос (метод/URL/статус/длительность/ошибка) | Метрики выключены (no-op) |
AuditMiddleware | События аудита соответствия (формат text/json, маскировка чувствительных заголовков) | Конфигурация text по умолчанию |
HeaderMiddleware | Добавление статических заголовков каждому запросу (CRLF проверяется при создании) | Без заголовков (сквозной) |
Предупреждение
TimeoutMiddleware не подходит для Download и запросов с WithStreamBody(true) — он отменяет контекст сразу после возврата handler (получения заголовков ответа), из-за чего чтение тела ответа завершается ошибкой "context canceled". В таких сценариях используйте WithTimeout.
Сессии и доменный клиент
Для последовательных запросов к одному домену (состояние входа, общие заголовки, проброс Cookie) используйте DomainClient вместо ручной склейки URL и передачи Cookie на каждый запрос:
dc, err := httpc.NewDomainDefault("https://api.example.com")
if err != nil {
log.Fatal(err)
}
defer dc.Close()
dc.SetHeader("Authorization", "Bearer "+token) // Сессионный заголовок: последующие запросы несут его автоматически
_, _ = dc.Post("/login", httpc.WithJSON(creds)) // Set-Cookie из ответа автоматически попадает в сессию
_, _ = dc.Get("/me") // Cookie сессии добавляются автоматическиРазделение ролей двух компонентов:
DomainClient— привязывает base URL; относительные пути склеиваются автоматически (/users→https://api.example.com/users), полныйhttp(s)://URL используется напрямую; встроенная защита от path traversal (выход результата склейки за пределы base-пути возвращает ошибку). При создании автоматически включает Cookie jar.SessionManager— потокобезопасное хранилище состояния сессии (Cookie + заголовки);DomainClientвстраивает его: перед каждым запросом состояние сессии добавляется в опции запроса, после ответа записываетсяSet-Cookie. Может использоваться и отдельно отDomainClient(NewSessionManagerDefault()).
Опции выполняются дважды
Опции запроса DomainClient внутри применяются дважды — один раз для захвата состояния сессии (Cookie/заголовки), один раз для выполнения реального запроса. Не помещайте в опции логику с побочными эффектами (счётчики, одноразовые nonce и т.п.).
Подробнее см. Доменный клиент и сессии.
Безопасные значения по умолчанию
HTTPC безопасен по умолчанию (secure by default) — без дополнительной конфигурации обеспечивает:
- принудительное шифрование TLS 1.2+
- Защиту от SSRF — блокировку подключений к приватным/зарезервированным IP-адресам (
127.0.0.1,10.x,192.168.xи др.) - Защиту от CRLF-инъекций — автоматическую валидацию заголовков и URL
- Лимит размера тела ответа — 10MB по умолчанию, защита от исчерпания памяти
- Защиту от декомпрессионных бомб — лимит распакованного тела 100MB по умолчанию
- Строгую проверку Content-Length — включена по умолчанию, несовпадение длины тела с заявленной возвращает ошибку
Для подключения к внутренним сервисам (VPN, интранет) установите Security.AllowPrivateIPs = true или используйте точное исключение SSRFExemptCIDRs. Подробнее см. Обзор безопасности.
Модель ошибок
HTTPC различает ошибки сетевого уровня и HTTP-коды состояния:
- Ошибки сетевого уровня (сбой соединения, таймаут, ошибка TLS и т.п.) → возвращаются как
error; классификацию и повторяемость можно получить черезerrors.As, извлекаяClientError - HTTP-коды состояния (4xx, 5xx) → не возвращаются как
error; проверяйте их методами вродеresult.IsSuccess()
result, err := client.Get(url)
if err != nil {
// Ошибка сетевого уровня — запрос не завершён успешно
var clientErr *httpc.ClientError
if errors.As(err, &clientErr) {
log.Printf("Тип ошибки: %s, повторяемо: %v", clientErr.Code(), clientErr.IsRetryable())
}
return err
}
// Запрос завершён — проверяем HTTP-код состояния
if !result.IsSuccess() {
log.Printf("HTTP-ошибка: %d", result.StatusCode())
}Контекст, который несёт ClientError:
| Член | Описание |
|---|---|
Type | Классификация ошибки (12 значений перечисления: ErrorTypeTimeout, ErrorTypeNetwork и др.) |
Code() | Короткий строковый код: TIMEOUT, NETWORK_ERROR, TLS_ERROR, DNS_ERROR, CONTEXT_CANCELED, VALIDATION_ERROR, HTTP_ERROR и др. |
IsRetryable() | Стоит ли повторять (отмена контекста/валидация/TLS/сертификаты — всегда false; таймаут/транспорт — всегда true; сеть/DNS/5xx — зависит от причины) |
Attempts | Число выполненных попыток (включая первую) |
StatusCode | Связанный HTTP-код состояния (если применим) |
Cause | Исходная ошибка, прозрачна для errors.Is / errors.As |
URL / Method | Маскированные URL и метод запроса |
Частые сигнальные ошибки проверяются через errors.Is: ErrClientClosed (использование закрытого клиента), ErrResponseBodyEmpty (пустое тело ответа в Unmarshal), ErrResponseBodyTooLarge (тело для парсинга свыше 50MB) и др.; полный список — в Типах ошибок.
Подробнее см. Обработка ошибок.
Конкурентность и управление ресурсами
- Безопасность Client для конкурентного доступа — один клиент может делиться между произвольным числом goroutine, пул соединений внутри управляется по хостам, отдельный клиент для конкурентности не нужен.
- Result независим и не требует освобождения — каждый запрос возвращает новый
*Result(с тремя структурами метаданных, размещаемыми одним выделением памяти), его хранение не создаёт обязательств по жизненному циклу — отдайте GC. - Явный Close —
client.Close()высвобождает пул соединений и ресурсы транспортного уровня; запрос после закрытия возвращаетErrClientClosed. - Самовосстановление клиента по умолчанию — клиент пакетных функций можно заменить через
SetDefaultClient()и закрыть черезCloseDefaultClient(); после закрытия следующий пакетный вызов пересоздаст его автоматически. - Страховочная сетка от паник — внутри
Requestесть запасной recover: неожиданная паника на пути выполнения преобразуется вerrorсо стеком, а не пробивает вызывающую сторону.