Skip to content

Основные концепции

Поняв перечисленные ниже концепции, вы быстро составите целостное представление об HTTPC.

Обзор ключевых компонентов

КомпонентОтветственностьКлючевые моменты
Client (интерфейс)Выполнение запросов, управление пулом соединений и жизненным цикломСоздаётся New(cfg) / NewDefault(); 7 глагольных методов + Request + Download + Close
Doer (интерфейс)Минимальный интерфейс запросаЕдинственный метод Request(ctx, method, url, opts...); для mock и пользовательских реализаций
RequestOption (функции With*)Формирование отдельного запросаФункциональные опции; применяются в порядке передачи, любая ошибка немедленно прерывает запрос
MiddlewareFunc / HandlerMiddleware и конечный обработчикЛуковая модель; композиция через Chain(mw...)
RequestMutator / ResponseMutatorПредставления чтения/записи запроса/ответа внутри middlewareНа фазе запроса читают/пишут запрос, на фазе ответа — ответ
SessionManagerХранилище состояния сессииПотокобезопасен; единообразно управляет Cookie и общими заголовками
DomainClienter (интерфейс)Доменный клиентПривязка base URL + встроенная сессия; относительные пути склеиваются автоматически
ResultОбёртка ответаТрёхчастная структура «запрос / ответ / метаданные»; nil-безопасные методы доступа; автоматическая сборка GC
ClientErrorКлассификация ошибок сетевого уровняИзвлекается через errors.As; Code() / IsRetryable() / Attempts

Связи между компонентами:

text
Функции пакета ──используют──▶ 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:

Функции пакета — нулевая конфигурация, внутри общий лениво инициализируемый клиент по умолчанию; подходят для скриптов и разовых запросов:

go
result, err := httpc.Get("https://api.example.com/data")

Экземпляр Client — полный контроль над конфигурацией, пулом соединений и жизненным циклом; подходит для долго работающих сервисов:

go
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() и меняйте поля по необходимости:

go
cfg := httpc.DefaultConfig()
cfg.Timeouts.Request = 60 * time.Second
cfg.Retry.MaxRetries = 5
client, err := httpc.New(cfg)

Опции запроса передаются при каждом вызове, дополняя или переопределяя значения уровня экземпляра:

go
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.

Жизненный цикл запроса

Каждый запрос проходит следующие этапы:

text
Применение опций → Цепочка 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 — сигнатура функции, реально обрабатывающей запрос:

go
type Handler func(ctx context.Context, req RequestMutator) (ResponseMutator, error)
type MiddlewareFunc func(Handler) Handler

Middleware регистрируются в Config.Middleware.Middlewares и комбинируются Chain(middlewares...) в луковую модель: оборачивают в порядке регистрации — первое middleware является самым внешним слоем; фаза запроса выполняется по порядку, фаза ответа — в обратном.

Каркас пользовательского middleware:

go
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 на каждый запрос:

go
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; относительные пути склеиваются автоматически (/usershttps://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()
go
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.
  • Явный Closeclient.Close() высвобождает пул соединений и ресурсы транспортного уровня; запрос после закрытия возвращает ErrClientClosed.
  • Самовосстановление клиента по умолчанию — клиент пакетных функций можно заменить через SetDefaultClient() и закрыть через CloseDefaultClient(); после закрытия следующий пакетный вызов пересоздаст его автоматически.
  • Страховочная сетка от паник — внутри Request есть запасной recover: неожиданная паника на пути выполнения преобразуется в error со стеком, а не пробивает вызывающую сторону.