Skip to content

Часто задаваемые вопросы

Как выбрать: пакетные функции или экземпляр Client?

Ответ: Пакетные функции (httpc.Get/httpc.Post и др.) внутренне используют глобальный общий клиент по умолчанию (defaultClient), лениво инициализируемый при первом вызове и автоматически восстанавливаемый после закрытия. Подходят для одноразовых запросов, скриптов, CLI-утилит и других сценариев, не требующих пользовательской конфигурации.

go
// Пакетная функция: просто и быстро, совместно использует пул соединений клиента по умолчанию
result, err := httpc.Get("https://api.example.com/data")

Создавайте явный экземпляр Client при любом из следующих сценариев:

  • Пользовательская конфигурация (таймауты, прокси, повторные попытки, TLS и др.)
  • Независимое управление жизненным циклом пула соединений
  • Использование цепочки middleware (логирование/аудит/метрики/ID запроса)
  • Сосуществование нескольких клиентов с разными конфигурациями
go
// Явный Client: полный контроль над конфигурацией и жизненным циклом
client, err := httpc.New(httpc.PerformanceConfig())
if err != nil {
    log.Fatal(err)
}
defer func() { _ = client.Close() }()

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

Если нужно, чтобы пакетные функции использовали пользовательскую конфигурацию, замените глобальный клиент через SetDefaultClient (старый будет автоматически закрыт):

go
customClient, _ := httpc.New(httpc.SecureConfig())
if err := httpc.SetDefaultClient(customClient); err != nil {
    log.Fatal(err)
}
// С этого момента все пакетные функции используют customClient

Рекомендация для продакшена

В долгоживущих сервисах предпочтите явный Client, избегая неявной связи через глобальное состояние. Пакетные функции подходят только для программ с коротким жизненным циклом или быстрых прототипов.

Как выбрать из пяти конфигурационных пресетов?

Ответ: HTTPC предоставляет пять пресетов конфигурации, расположенных по балансу безопасности и производительности:

ПресетТаймаутПовторыПеренаправленияSSRFЛимит ответаПроверка TLSСценарий применения
SecureConfig()Строгий (15s)1ЗапрещеныВключена5MBВключенаОбработка пользовательских URL, финансы/медицина
DefaultConfig()Умеренный (180s)3РазрешеныВключена10MBВключенаУниверсальные сценарии
PerformanceConfig()Длиннее (60s)3РазрешеныВключена50MBВключенаВнутренние микросервисы, высоконагруженные API
MinimalConfig()Умеренный0ЗапрещеныВключена1MBВключенаОдноразовые скрипты, простые вызовы
TestingConfig()Короткий (5s Dial)1РазрешеныОтключенаПо умолчаниюПропущенаМодульные тесты, локальная разработка

Дерево решений:

Обработка пользовательских URL?
├── Да → SecureConfig()
└── Нет → Нужна ли высокая пропускная способность?
         ├── Да → PerformanceConfig()
         └── Нет → Одноразовый запрос?
                  ├── Да → MinimalConfig()
                  └── Нет → DefaultConfig()
Тестовая среда → TestingConfig()

Риск безопасности TestingConfig

TestingConfig() отключает проверку TLS и защиту SSRF, при использовании вне тестовой среды выводит предупреждение. Категорически запрещено в продакшене — только для файлов *_test.go или локальной разработки.

Как настроить HTTP/SOCKS5-прокси?

Ответ: HTTPC предоставляет три способа настройки прокси по приоритету: ProxyURL > ProxyPool > EnableSystemProxy.

СпособПолеСценарий примененияОсобенности
Одиночный проксиProxyURLФиксированный прокси-серверВысший приоритет, прямое указание
Пул проксиProxyPoolРотация нескольких прокси, высокая доступностьПоддержка стратегий ротации и пассивного выключения
Системный проксиEnableSystemProxyЧтение переменных окруженияНизший приоритет, следует конфигурации системы
go
// Способ 1: одиночный прокси (поддержка протоколов http/https/socks5)
cfg := httpc.DefaultConfig()
cfg.Connection.ProxyURL = "socks5://user:pass@proxy:1080"
client, _ := httpc.New(cfg)

// Способ 2: системный прокси (чтение переменных окружения HTTP_PROXY/HTTPS_PROXY)
cfg.Connection.EnableSystemProxy = true

// Способ 3: пул прокси (ротация нескольких прокси + пассивное выключение)
cfg.Connection.ProxyPool = []string{
    "http://proxy1:8080",
    "http://proxy2:8080",
    "socks5://proxy3:1080",
}
cfg.Connection.ProxyPoolStrategy = httpc.ProxyStrategyRoundRobin

Каков принцип ротации пула прокси?

Ответ: Пул прокси реализует ротацию IP через три механизма:

1. Стратегийная ротация (при каждом выборе): ProxyStrategyRoundRobin циклически выбирает по порядку, при каждом выборе переход к следующему прокси, поэтому при повторе естественно попадает на другой IP без дополнительной настройки. ProxyStrategyRandom выбирает случайно из здоровых прокси.

2. Ротация для каждого запроса (при начале запроса): установите ProxyRotatePerRequest = true, при начале каждого независимого запроса закрываются все простаивающие соединения, что принуждает Transport заново оценить пул прокси. Без включения повторное использование HTTP-соединений приводит к тому, что последовательные запросы к одному хосту будут повторно использовать прокси-туннель предыдущего запроса, обходя стратегийную ротацию. Цена — отсутствие повторного использования соединений (новое соединение для каждого запроса), но гарантируется ротация по запросам. Подходит для скрапинга/сбора данных с одного хоста — каждый запрос имеет разный исходный IP.

3. Ротация по коду состояния (при ответе): установите ProxyRotateOnStatus (например, []int{403}), при получении ответа с этими кодами и Retry.MaxRetries > 0 запускается повтор, стратегийная ротация при повторе обеспечивает смену IP. Подходит для обхода CF/WAF и других блокировок на уровне IP.

Кроме того, пул прокси имеет встроенные пассивное выключение и автоматическое восстановление: после ProxyFailureThreshold (по умолчанию 3) последовательных сбоев соединения (dial/TLS) прокси временно удаляется из пула ротации, после ProxyCooldown (по умолчанию 30s) восстанавливается через half-open пробу. Обратите внимание: HTTP-коды состояния не вызывают выключение — поскольку блокировка часто специфична для целевого сайта (прокси, заблокированный на одном сайте, может нормально работать на другом).

go
cfg := httpc.DefaultConfig()
cfg.Connection.ProxyPool = []string{
    "http://proxy1:8080",
    "http://proxy2:8080",
    "http://proxy3:8080",
}
cfg.Connection.ProxyPoolStrategy = httpc.ProxyStrategyRoundRobin
cfg.Connection.ProxyFailureThreshold = 3   // Выключение после 3 последовательных сбоев соединения
cfg.Connection.ProxyCooldown = 30 * time.Second // Half-open восстановление через 30s
cfg.Connection.ProxyRotateOnStatus = []int{403} // Смена IP и повтор при 403
cfg.Retry.MaxRetries = 3 // ProxyRotateOnStatus требует совместного использования с повторами

Как настроить DoH?

Ответ: DNS-over-HTTPS (DoH) снижает задержку разрешения DNS, предотвращает перехват DNS и отравление кэша. Способ включения:

go
cfg := httpc.DefaultConfig()
cfg.Connection.EnableDoH = true
cfg.Connection.DoHCacheTTL = 5 * time.Minute // Время кэширования DNS-ответов (по умолчанию 5 минут)

По умолчанию используются три провайдера: Cloudflare, Google, AliDNS (с откатом по приоритету). При недоступности всех провайдеров DoH автоматически откатывается к системному DNS, гарантируя доступность.

Когда использовать DoH

DoH подходит для сценариев с высокими требованиями к безопасности разрешения DNS (например, защита от перехвата DNS провайдером). Для обычных API-вызовов включение не требуется — системный DNS обычно достаточен, а DoH добавляет небольшую задержку разрешения (первый запрос требует HTTPS-круга).

Ответ: HTTPC предоставляет двухуровневое управление Cookie:

1. Автоматическое управление (DomainClient): DomainClient, созданный через NewDomain, автоматически включает Cookie (EnableCookies=true) и встраивает SessionManager. После каждого запроса UpdateFromResult автоматически фиксирует Set-Cookie из ответа, перед следующим запросом prepareOptions автоматически внедряет. Подходит для сценариев поддержания сессии входа.

2. Ручное управление (SessionManager): при необходимости более точного контроля напрямую оперируйте *SessionManager, возвращаемым dc.Session() — установка/удаление/запрос Cookie, переключение политики безопасности во время выполнения, массовое извлечение из ответов.

go
// Автоматическое управление: сессия автоматически поддерживается после входа
dc, _ := httpc.NewDomain("https://api.example.com", httpc.DefaultConfig())
defer func() { _ = dc.Close() }()

// Вход (Set-Cookie ответа автоматически фиксируется)
_, _ = dc.Request(ctx, "POST", "/login", httpc.WithJSON(loginData))

// Последующие запросы автоматически несут Cookie сессии
result, _ := dc.Request(ctx, "GET", "/profile")

Для обычного Client установка cfg.Connection.EnableCookies = true включает автоматическое управление cookie jar, но без автоматического извлечения в SessionManager.

Подробнее см. Управление сессиями.

Как настроить повторы?

Ответ: HTTPC по умолчанию выполняет 3 повтора только для повторяемых транзитных ошибок:

Условия повторов по умолчанию (действуют без настройки):

  • Сетевые ошибки: отказ соединения, сброс соединения, сеть недоступна и др.
  • Транспортные таймауты: таймаут net.OpError (не дедлайн контекста)
  • Определённые HTTP-коды состояния: 408 (таймаут запроса), 429 (rate limit), 500, 502, 503, 504

Разбор заголовка Retry-After: при получении 429/503 с заголовком Retry-After HTTPC автоматически ожидает по указанной сервером задержке (а не вычисляет откат самостоятельно), избегая усиления нагрузки на сервер.

Пользовательские повторы: реализуйте интерфейс RetryPolicy (методы ShouldRetry + GetDelay) для замены встроенной логики, присвойте cfg.Retry.CustomPolicy. Подробнее см. Повторные попытки и отказоустойчивость.

go
cfg := httpc.DefaultConfig()
cfg.Retry.MaxRetries = 5              // Максимум 5 повторов (лимит 10)
cfg.Retry.Delay = 2 * time.Second     // Начальная задержка
cfg.Retry.BackoffFactor = 2.0         // Множитель экспоненциального отката
cfg.Retry.MaxRetryDelay = 60 * time.Second // Максимальная одна задержка
cfg.Retry.EnableJitter = true         // Джиттер (защита от эффекта стада)

Отмена context не повторяется

Сбои, вызванные context.Canceled и context.DeadlineExceeded, никогда не повторяются — это явное намерение пользователя об отмене/таймауте, повтор нарушил бы это намерение.

Подробнее см. Повторные попытки и отказоустойчивость.

Как выбрать таймауты?

Ответ: HTTPC предоставляет четырёхуровневую систему таймаутов по области действия от широкой к узкой:

Уровень таймаутаПолеПо умолчаниюОбласть действияПереопределение на уровне запроса
Общий таймаут запросаTimeouts.Request180sВесь процесс включая повторыWithTimeout()
Таймаут установки соединенияTimeouts.Dial10sУстановка TCP-соединенияНет
Таймаут TLS-рукопожатияTimeouts.TLSHandshake10sTLS-рукопожатие (только HTTPS)Нет
Таймаут простаивающего соединенияTimeouts.IdleConn90sВремя жизни простаивающего соединенияНет
Таймаут заголовков ответаTimeouts.ResponseHeader0 (отключено)Ожидание прибытия заголовков ответаНевозможно переопределить

Особое поведение ResponseHeader: по умолчанию 0 (отключено), при этом таймаут контекста Timeouts.Request или WithTimeout() полностью контролирует ситуацию. При установке положительного значения включается транспортный жёсткий лимит (защита от slowloris), но это значение переопределяет WithTimeout (если ResponseHeader короче) и применяется ко всем запросам, разделяющим один client — переопределение по запросу невозможно.

go
// Рекомендуется: таймаут контекста (точный контроль, поэтапная установка)
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
result, _ := client.Request(ctx, "GET", url)

// Переопределение опцией запроса (внутренне преобразуется в таймаут контекста)
result, _ := client.Get(url, httpc.WithTimeout(30*time.Second))

TimeoutMiddleware не подходит для Download

TimeoutMiddleware немедленно отменяет контекст после возврата handler (defer cancel()), а handler Download возвращает управление после получения заголовков ответа — в этот момент тело ещё не обработано, cancel вызовет "context canceled" на первом байте тела. Не используйте TimeoutMiddleware для обёртки Download, вместо этого применяйте WithTimeout (его дедлайн действует на общий контекст движка, охватывая чтение тела).

Почему 4xx/5xx не возвращаются как error?

Ответ: Это дизайнерское решение HTTPC: HTTP-коды состояния — семантика уровня приложения, а не ошибки транспортного уровня. Ответ с 404 полностью успешен на сетевом уровне — TCP-соединение установлено, TLS-рукопожатие завершено, HTTP-запрос доставлен, ответ корректно возвращён. Рассмотрение этого как error перепутало бы с сетевыми ошибками и усложнило обработку.

Поэтому HTTPC возвращает error только при сетевых сбоях (отказ соединения, таймаут, сбой DNS и др.), а HTTP-коды состояния проверяются через Result:

go
result, err := client.Get(url)
if err != nil {
    // Сетевая ошибка (сбой соединения, таймаут и др.)
    var clientErr *httpc.ClientError
    if errors.As(err, &clientErr) {
        log.Printf("Сетевая ошибка: %s", clientErr.Code())
    }
    return err
}

// Проверка HTTP-кодов состояния
switch {
case result.IsSuccess():      // 2xx
    handleSuccess(result)
case result.IsClientError():  // 4xx
    log.Printf("Ошибка клиента: %d", result.StatusCode())
case result.IsServerError():  // 5xx
    log.Printf("Ошибка сервера: %d", result.StatusCode())
}

Как обрабатывать загрузку больших файлов?

Ответ: Используйте метод Download для потоковой загрузки с поддержкой колбэка прогресса, докачки, проверки SHA-256 и проверкой безопасности пути:

go
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/cybergodev/httpc"
)

func main() {
	client, err := httpc.NewDefault()
	if err != nil {
		log.Fatalf("Не удалось создать клиент: %v", err)
	}
	defer func() { _ = client.Close() }()

	cfg := httpc.DefaultDownloadConfig()
	cfg.FilePath = "/tmp/large-file.zip"
	cfg.Overwrite = true
	cfg.ResumeDownload = true // Докачка: после прерывания повторная загрузка продолжится с точки останова
	cfg.Checksum = "e3b0c44298fc1c149afbf4c8996fb924..." // Ожидаемый SHA-256
	cfg.ChecksumAlgorithm = httpc.ChecksumSHA256
	cfg.ProgressCallback = func(downloaded, total int64, speed float64) {
		if total > 0 {
			fmt.Printf("\r%.1f%% (%s / %s, %s)",
				float64(downloaded)/float64(total)*100,
				httpc.FormatBytes(downloaded),
				httpc.FormatBytes(total),
				httpc.FormatSpeed(speed))
		}
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
	defer cancel()

	result, err := client.Download(ctx, "https://example.com/large-file.zip", cfg)
	if err != nil {
		log.Fatalf("Сбой загрузки: %v", err)
	}
	fmt.Printf("\nЗагрузка завершена: %s (%d байт)\n", result.FilePath, result.BytesWritten)
}

Безопасность пути

Download проверяет FilePath, отклоняя пути-каталоги (возврат ошибки открытия файла) и неподдерживаемые алгоритмы проверки (отклонение до касания целевого файла). Докачка зависит от поддержки сервером Range-запросов — если сервер возвращает 200 вместо 206, HTTPC корректно обрабатывает это (начинает с начала, а не обрезает).

Как сделать закрепление сертификатов?

Ответ: Закрепление сертификатов (Certificate Pinning) на этапе TLS-рукопожатия проверяет, соответствует ли публичный ключ сервера предзаданному отпечатку, и даже при компрометации доверенного CA защищает от атак «человек посередине».

Шаги генерации SPKI-хеша (с использованием OpenSSL):

bash
openssl x509 -in cert.pem -pubkey -noout | openssl pkey -pubin -outform der \
  | openssl dgst -sha256 -binary | openssl enc -base64

Ротация с несколькими хешами: передача нескольких хешей поддерживает ротацию ключей — проходит, если ключ сервера соответствует любому из предзаданных хешей. Рекомендуется всегда настраивать один резервный хеш, чтобы при ротации ключа сервера клиент не терял соединение.

go
pinner, err := httpc.NewSPKIHashPinner(
    "YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2fuihg=", // Текущий ключ
    "C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=", // Резервный ключ (для ротации)
)
if err != nil {
    log.Fatal(err)
}

cfg := httpc.DefaultConfig()
cfg.Security.CertificatePinner = pinner
client, _ := httpc.New(cfg)

Экземпляр Pinner может безопасно разделяться несколькими Client (внутренне потокобезопасен и не глубоко копируется).

Будет ли запрос повторяться после отмены context?

Ответ: Нет. context.Canceled и context.DeadlineExceeded неповторяемы — они представляют явное намерение пользователя об отмене или таймауте, повтор нарушил бы это намерение.

Метод IsRetryable() HTTPC приоритетно проверяет ошибки контекста перед всеми остальными проверками: пока в цепочке Cause присутствует context.Canceled или context.DeadlineExceeded, возвращается false. Даже если ошибка классифицирована как ErrorTypeNetwork (обычно повторяемая), она будет корректно определена как неповторяемая из-за проверки контекстной ошибки.

go
ctx, cancel := context.WithCancel(context.Background())
go func() {
    time.Sleep(100 * time.Millisecond)
    cancel() // Активная отмена
}()

_, err := client.Request(ctx, "GET", "https://example.com/slow")
if err != nil {
    var clientErr *httpc.ClientError
    if errors.As(err, &clientErr) {
        fmt.Println(clientErr.Type == httpc.ErrorTypeContextCanceled) // true
        fmt.Println(clientErr.IsRetryable())                          // false
    }
}

Почему MaxRedirects(0) не запрещает перенаправления?

Ответ: MaxRedirects = 0 — сигнальное значение «не задано», а не «запретить перенаправления». В DefaultConfig() MaxRedirects по умолчанию равен 10. Для действительного запрета перенаправлений используйте WithFollowRedirects(false) или установите Config.Defaults.FollowRedirects = false:

go
// Способ 1: запрет на уровне конфигурации
cfg := httpc.DefaultConfig()
cfg.Defaults.FollowRedirects = false
client, _ := httpc.New(cfg)

// Способ 2: запрет на уровне запроса
result, _ := client.Get(url, httpc.WithFollowRedirects(false))

Пресет SecureConfig() по умолчанию запрещает перенаправления (FollowRedirects = false), предотвращая SSRF-атаки через перенаправления.

Почему тело запроса io.Reader не проверяется по размеру?

Ответ: io.Reader — потоковый интерфейс, невозможно заранее узнать длину данных — метод Len() не существует, чтение означает потребление. Поэтому HTTPC не выполняет проверку размера для тела запроса типа io.Reader, ответственность за контроль объёма данных лежит на вызывающем.

Для ограничения размера загрузки оберните через io.LimitReader стандартной библиотеки:

go
// Ограничение загрузки максимум 1MB
limitedReader := io.LimitReader(unlimitedReader, 1024*1024)
result, err := client.Post(url, httpc.WithBody(limitedReader))

Или настройте Security.MaxRequestBodySize для глобального лимита загрузки (по умолчанию 0 = без лимита):

go
cfg := httpc.DefaultConfig()
cfg.Security.MaxRequestBodySize = 10 * 1024 * 1024 // Глобальный лимит 10MB

Как подавить предупреждения безопасности?

Ответ: TestingConfig() и подобные небезопасные конфигурации при использовании вне тестовой среды выводят предупреждения через log.Printf. Для подавления (например, в специфических сценариях CI) перенаправьте вывод предупреждений в io.Discard:

go
// Подавление всех выводов предупреждений безопасности
httpc.SetSecurityWarnOutput(io.Discard)

cfg := httpc.TestingConfig() // Предупреждения больше не выводятся

Только для контролируемых сред

Подавление предупреждений безопасности лишь скрывает вывод и не восстанавливает отключённые функции безопасности. Используйте только в контролируемых средах (CI, контейнеризированные тесты); в продакшене используйте SecureConfig() или DefaultConfig().

Как получить доступ к внутренним сервисам?

Ответ: По умолчанию защита SSRF блокирует соединения с приватными IP (127.0.0.1, 10.x, 192.168.x, 169.254.x и др.). Доступ к внутренним сервисам двумя способами:

go
// Способ 1: точное освобождение (рекомендуется) — разрешает только указанные диапазоны CIDR
cfg := httpc.DefaultConfig()
cfg.Security.SSRFExemptCIDRs = []string{
    "10.0.0.0/8",     // Внутренний VPC
    "100.64.0.0/10",  // Tailscale/VPN
}

// Способ 2: полное отключение (опасно) — отключает все SSRF-проверки диалера на уровне соединений
cfg.Security.AllowPrivateIPs = true

Риск AllowPrivateIPs

AllowPrivateIPs = true не только разрешает приватные IP, но и полностью обходит SSRF-проверки диалера на уровне соединений (включая проверки localhost/loopback/link-local). Используйте только при подключении к доверенным внутренним сервисам; при обработке пользовательских URL категорически запрещено включать — используйте точное освобождение через SSRFExemptCIDRs.

Как логировать запросы?

Ответ: Используйте LoggingMiddleware для добавления логирования запросов; URL автоматически маскируется, предотвращая утечку учётных данных:

go
cfg := httpc.DefaultConfig()
cfg.Middleware.Middlewares = []httpc.MiddlewareFunc{
    httpc.LoggingMiddleware(&httpc.LoggingConfig{
        LogFunc: func(format string, args ...any) {
            log.Printf("[HTTP] "+format, args...)
        },
    }),
}
client, _ := httpc.New(cfg)

Для комплаенс-аудита (запись заголовков запроса/ответа, цепочки перенаправлений, исходного IP, ID пользователя) используйте более функциональный AuditMiddleware. Подробнее см. Справочник middleware.

Дополнительные ресурсы