Прокси и пул прокси
Будь то обход корпоративной сети, ротация исходных IP для задач сбора данных или обход IP-блокировок на целевом сайте — прокси остаются частой потребностью HTTP-клиента. В HTTPC встроены четыре режима прокси — одиночный прокси, обнаружение системного прокси, ротация пула и ротация по статус-коду — покрывающие весь спектр от «фиксированного выхода» до «смены IP на каждый запрос» и работающие совместно с защитой от SSRF, проверкой TLS и движком повторов. Вся конфигурация прокси сосредоточена в ConnectionConfig.
Обзор режимов прокси
Четыре режима применяются автоматически по приоритету; при одновременной настройке нескольких действует только режим с высшим приоритетом:
| Приоритет | Настройка | Поведение | Типичный сценарий |
|---|---|---|---|
| 1 (высший) | ProxyURL | Всегда использовать указанный прокси (режим одиночного прокси) | Выход корпоративной сети, локальный VPN-порт |
| 2 | ProxyPool | Ротация в пуле прокси с размыканием и восстановлением | Сбор данных, распределение нагрузки, ротация IP |
| 3 | EnableSystemProxy | Автообнаружение системных настроек прокси | Десктоп-приложения, следующие настройкам пользователя |
| 4 (низший) | Нет | Прямое подключение | Поведение по умолчанию |
Подсказка
Если заданы одновременно ProxyURL и ProxyPool, применяется ProxyURL. Чтобы использовать пул прокси, очистите ProxyURL.
Одиночный прокси
ProxyURL задаёт фиксированный прокси, поддерживаются четыре протокола:
| Протокол | Запись | Описание |
|---|---|---|
| HTTP | http://proxy:8080 | Самый распространённый; HTTPS-запросы идут через CONNECT-туннель |
| HTTPS | https://proxy:8443 | TLS используется и для соединения с самим прокси-сервером |
| SOCKS5 | socks5://proxy:1080 | Целевой домен разрешается локально, соединение устанавливается через прокси |
| SOCKS5h | socks5h://proxy:1080 | Разрешение домена выполняется на стороне прокси, обходит локальное загрязнение DNS |
package main
import (
"log"
"github.com/cybergodev/httpc"
)
func main() {
cfg := httpc.DefaultConfig()
cfg.Connection.ProxyURL = "socks5://proxy.example.com:1080"
client, err := httpc.New(cfg)
if err != nil {
log.Fatal(err)
}
defer client.Close()
result, err := client.Get("https://api.example.com/data")
if err != nil {
log.Fatal(err)
}
// Вывод: статус: 200, прокси: socks5://proxy.example.com:1080
log.Printf("статус: %d, прокси: %s", result.StatusCode(), result.Meta.ProxyURL)
}Аутентификация и маскирование
Учётные данные прокси указываются прямо в userinfo-части URL:
cfg := httpc.DefaultConfig()
cfg.Connection.ProxyURL = "http://user:[email protected]:8080"Автоматическое маскирование учётных данных
Config.String() заменяет имя пользователя и пароль в URL прокси на ***:***; URL в сообщениях ошибок и логах маскируется так же (учётные данные и чувствительные параметры запроса скрываются). Учётные данные не утекут в логи, но саму конфигурацию всё равно нужно хранить надёжно.
Обнаружение системного прокси и NO_PROXY
После включения автоматически определяются настройки прокси операционной системы — задавать ProxyURL вручную не нужно:
cfg := httpc.DefaultConfig()
cfg.Connection.EnableSystemProxy = trueОсобенности платформ
| Платформа | Источник обнаружения |
|---|---|
| Windows | Реестр Internet Settings (ProxyEnable / ProxyServer) |
| macOS | Чтение Web/Secure Web Proxy предпочтительной сетевой службы командой networksetup |
| Linux | Переменные окружения HTTP_PROXY / HTTPS_PROXY |
Meta.ProxyURL не охватывает системный прокси
Выбор системного прокси не записывается в Result.Meta.ProxyURL (поле имеет значение только при явном Connection.ProxyURL или ProxyPool; при прямом соединении и системном прокси оно остаётся пустым). Если нужно отслеживать исходящий прокси для каждого запроса, используйте явную конфигурацию.
Порядок и детали обнаружения
- Переменные окружения приоритетны (на всех платформах): сначала читаются
HTTP_PROXY/HTTPS_PROXY/NO_PROXY(регистр не важен); при наличии значения используются сразу, системные настройки не запрашиваются. - Платформенное обнаружение как запасной вариант: без переменных окружения читаются настройки платформы. Прокси рабочего стола Linux (GNOME/KDE) обычно уже экспортированы сессией в переменные окружения — движок не читает gsettings/dconf напрямую.
- Разделение запросов: HTTPS-запросы предпочитают
HTTPS_PROXY, при отсутствии откатываются кHTTP_PROXY; HTTP-запросы используют толькоHTTP_PROXY, а при его отсутствии тоже откатываются кHTTPS_PROXY— так же, как в net/http. - Прямое подключение в CGI-окружении: прокси из переменных окружения не действует в CGI-среде (когда задан
REQUEST_METHOD) — поведение как в net/http. - Автодополнение «голого» адреса: значения без префикса протокола вроде
HTTP_PROXY=proxy:8080автоматически трактуются какhttp://. - Кэширование: результат обнаружения кэшируется на время жизни клиента и не обновляется при изменении переменных окружения; чтобы подхватить изменения, создайте новый клиент.
Правила обхода NO_PROXY
NO_PROXY перечисляет хосты, которые не должны идти через прокси; семантика совпадает с пакетом httpproxy из net/http:
| Правило | Пример | Область сопоставления |
|---|---|---|
| Обход всего | * | Все хосты — прямое подключение |
| Суффикс домена | example.com или .example.com | Домен и все его поддомены |
| Поддомены по маске | *.example.com | Эквивалент .example.com |
| Литерал IP | 10.0.0.5 | Точное совпадение IP |
| CIDR-диапазон | 10.0.0.0/8 | Все IP диапазона |
| Хост + порт | example.com:443 | Хост совпадает и порт точно равен |
Несколько правил разделяются запятыми; localhost всегда подключается напрямую — прописывать его в NO_PROXY не нужно.
# Пример настройки Linux/macOS
export HTTPS_PROXY=http://proxy.corp.example.com:8080
export NO_PROXY=localhost,127.0.0.1,.internal.corp.com,10.0.0.0/8# Пример настройки Windows (PowerShell)
$env:HTTPS_PROXY = "http://proxy.corp.example.com:8080"
$env:NO_PROXY = "localhost,127.0.0.1,.internal.corp.com"Ограничение динамического localhost-прокси
В режиме системного прокси список освобождений SSRF сканируется один раз при построении клиента. Если за время работы системный прокси сменился на новый внутренний/loopback-адрес (например 127.0.0.1), этот адрес может быть заблокирован защитой от SSRF. В динамических сценариях задавайте явно Connection.ProxyURL (адрес прокси всегда освобождён от SSRF-проверок) или настройте SSRFExemptCIDRs.
Пул прокси
Когда запросы нужно распределять между несколькими прокси-IP (сбор данных, распределение нагрузки, ротация IP), пул прокси даёт автоматическую ротацию, пассивное размыкание и смену прокси по статус-коду — без каких-либо внешних компонентов.
Базовое использование
package main
import (
"log"
"github.com/cybergodev/httpc"
)
func main() {
cfg := httpc.DefaultConfig()
cfg.Connection.ProxyPool = []string{
"http://proxy1:8080",
"http://proxy2:8080",
"http://proxy3:8080",
}
cfg.Connection.ProxyPoolStrategy = httpc.ProxyStrategyRoundRobin // значение по умолчанию, можно опустить
client, err := httpc.New(cfg)
if err != nil {
log.Fatal(err)
}
defer client.Close()
result, err := client.Get("https://api.example.com/data")
if err != nil {
log.Fatal(err)
}
// Вывод: статус: 200, прокси: http://proxy1:8080 (следующий запрос автоматически получит proxy2)
log.Printf("статус: %d, прокси: %s", result.StatusCode(), result.Meta.ProxyURL)
}Записи пула поддерживают протоколы http, https, socks5, socks5h и могут смешиваться.
Поля конфигурации
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
ProxyPool | []string | nil | Список URL прокси |
ProxyPoolStrategy | ProxyStrategy | ProxyStrategyRoundRobin | Стратегия выбора |
ProxyFailureThreshold | int | 3 (0 → значение по умолчанию) | Порог последовательных сбоев соединения для размыкания |
ProxyCooldown | time.Duration | 30s (0 → значение по умолчанию) | Время охлаждения разомкнутого прокси |
ProxyRotatePerRequest | bool | false | Принудительная смена прокси на каждый запрос (отключает переиспользование простаивающих соединений) |
ProxyRotateOnStatus | []int | nil | Статус-коды, запускающие повтор со сменой прокси (каждый в пределах 100–599) |
Стратегия выбора
| Стратегия | Константа | Описание |
|---|---|---|
| Round-robin (по умолчанию) | ProxyStrategyRoundRobin | Циклический выбор по порядку, курсор сдвигается при каждом выборе |
| Случайная | ProxyStrategyRandom | Равномерный случайный выбор из здоровых прокси |
Round-robin + повторы = автоматическая смена IP
Стратегия round-robin сдвигает курсор при каждом выборе, поэтому повтор, снова запускающий выбор, естественным образом попадает на следующий прокси — без какой-либо дополнительной настройки.
Пассивный размыкатель цепи
В пул прокси встроена пассивная проверка работоспособности. Размыкание запускают только сбои на уровне соединения (dial/TLS); HTTP-статус-коды — нет:
Сбой соединения с прокси
↓
Счётчик сбоев +1
↓
Последовательных сбоев ≥ ProxyFailureThreshold → размыкание (исключение из ротации)
↓
Ожидание ProxyCooldown → полузакрытая проба (возврат в ротацию)
↓
Успех → сброс счётчика, замыкание
Первый сбой → повторное размыканиеcfg.Connection.ProxyFailureThreshold = 5 // терпимее к единичным сбоям
cfg.Connection.ProxyCooldown = 60 * time.Second // более долгое охлаждениеКогда разомкнуты все прокси, резервом возвращается прокси с кратчайшим временем охлаждения (ближе всего к восстановлению), а не немедленный отказ.
Ротация по статус-коду
Для сценариев IP-блокировок Cloudflare/WAF — при возврате определённых статус-кодов автоматически меняется прокси и выполняется повтор:
package main
import (
"log"
"github.com/cybergodev/httpc"
)
func main() {
cfg := httpc.DefaultConfig()
cfg.Connection.ProxyPool = []string{
"http://proxy1:8080",
"http://proxy2:8080",
"http://proxy3:8080",
}
cfg.Connection.ProxyRotateOnStatus = []int{403} // при 403 — смена прокси и повтор
cfg.Retry.MaxRetries = 3 // повторы должны быть включены
client, err := httpc.New(cfg)
if err != nil {
log.Fatal(err)
}
defer client.Close()
result, err := client.Get("https://protected-site.example.com/data")
if err != nil {
log.Fatal(err)
}
// Вывод: статус: 200, прокси: http://proxy2:8080, попыток: 2
log.Printf("статус: %d, прокси: %s, попыток: %d",
result.StatusCode(), result.Meta.ProxyURL, result.Meta.Attempts)
}Ротация по статус-коду ≠ размыкание
Ротация, запущенная ProxyRotateOnStatus, не размыкает прокси — IP-блокировки часто специфичны для цели (прокси, заблокированный на сайте A, может нормально работать на сайте B). Размыкание запускается только сбоями на уровне соединения. Для срабатывания требуется Retry.MaxRetries > 0.
Когда задан ProxyRotateOnStatus и в пуле несколько прокси, бюджет повторов автоматически повышается до len(ProxyPool) - 1 (с пределом MaxRetries = 10), чтобы каждый прокси получил шанс быть опробованным.
Ротация для каждого запроса
ProxyRotatePerRequest решает проблему закрепления прокси-туннеля из-за переиспользования соединений: пул HTTP-соединений переиспользует установленные TCP-соединения, включая их прокси-туннели. Это значит, что последовательные запросы к одному хосту используют прокси предыдущего запроса, даже если курсор ProxyPoolStrategy уже сдвинут.
После включения в начале каждого запроса закрываются все простаивающие соединения, вынуждая Transport заново оценить пул прокси — цена в отсутствии переиспользования соединений (новое соединение + прокси-туннель на каждый запрос), зато ротация по запросам гарантирована:
package main
import (
"log"
"github.com/cybergodev/httpc"
)
func main() {
cfg := httpc.DefaultConfig()
cfg.Connection.ProxyPool = []string{
"http://proxy1:8080",
"http://proxy2:8080",
"http://proxy3:8080",
}
cfg.Connection.ProxyRotatePerRequest = true // смена прокси на каждый запрос
client, err := httpc.New(cfg)
if err != nil {
log.Fatal(err)
}
defer client.Close()
for i := 0; i < 3; i++ {
result, err := client.Get("https://api.example.com/data")
if err != nil {
log.Fatal(err)
}
// Вывод (по порядку): http://proxy1:8080 / http://proxy2:8080 / http://proxy3:8080
log.Printf("Запрос %d через: %s", i+1, result.Meta.ProxyURL)
}
}Сценарии применения
Подходит для сбора данных с одного хоста — у каждого запроса свой исходный IP, что снижает риск IP-блокировки целевым сайтом. Для запросов к разным хостам переиспользование соединений не закрепляет один прокси, включать обычно не нужно.
Как и ProxyRotateOnStatus, ProxyRotatePerRequest при нескольких прокси в пуле автоматически повышает бюджет повторов до len(ProxyPool) - 1 (предел 10), гарантируя, что каждый прокси будет опробован хотя бы раз.
Детерминированная ротация
Повторы при ротации по статус-коду отличаются от обычных повторов: движок резервирует для каждого запроса базовый индекс прокси, и N-й повтор всегда использует прокси «база + N». Отсюда три гарантии:
- Повтор всегда меняет прокси: в рамках одного запроса N-й повтор обязательно попадает на другой прокси, чем (N-1)-й;
- Цепочка перенаправлений не сбивается: несколько переходов внутри одной попытки делят один индекс — следование перенаправлениям не расходует лишние выборы прокси и не смещает порядок;
- Ротация продолжается между запросами: базовые индексы разных запросов возрастают, сохраняя в целом round-robin/случайное распределение.
Кроме того, сбои соединения самого прокси (сбой dial/TLS) при активной ротации всегда повторяемы — даже если обычная классификация признаёт ошибку неповторяемой (например, постоянная ошибка адреса), следующая попытка сменит прокси, а пассивное размыкание естественно отсеет плохие прокси. Подробнее о стороне повторов — в Повторных попытках и отказоустойчивости.
Как узнать, какой прокси использовался
В сценариях с пулом прокси часто нужен аудит: «через какой выход реально прошёл этот запрос». Работают в паре два инструмента:
Result.Meta.ProxyURL: сообщает прокси, использованный попыткой, породившей итоговый ответ; при прямом подключении — пустая строка, системный прокси (EnableSystemProxy) также не записывается (остаётся пустым). При ротации каждая попытка может использовать разный прокси — поле соответствует последней попытке.- Колбэк
WithOnResponse: срабатывает на каждую попытку (включая повторы со сменой прокси), позволяя наблюдать статус-коды и число попыток по мере выполнения.
package main
import (
"log"
"github.com/cybergodev/httpc"
)
func main() {
cfg := httpc.DefaultConfig()
cfg.Connection.ProxyPool = []string{
"http://proxy1:8080",
"http://proxy2:8080",
"http://proxy3:8080",
}
cfg.Connection.ProxyRotateOnStatus = []int{403}
client, err := httpc.New(cfg)
if err != nil {
log.Fatal(err)
}
defer client.Close()
result, err := client.Get("https://protected-site.example.com/data",
httpc.WithOnResponse(func(resp httpc.ResponseMutator) error {
log.Printf("Попытка %d получила %d", resp.Attempts(), resp.StatusCode())
return nil
}),
)
if err != nil {
log.Fatal(err)
}
// Вывод: итоговый прокси: http://proxy2:8080, всего попыток: 2
log.Printf("итоговый прокси: %s, всего попыток: %d", result.Meta.ProxyURL, result.Meta.Attempts)
}Колбэки выполняются по попыткам, middleware — по запросам
WithOnResponse срабатывает внутри движка на каждую попытку (включая повторы); цепочка middleware оборачивает весь цикл повторов и на один логический запрос выполняется единожды. Для наблюдения за отдельными попытками используйте колбэк, для аудита запроса целиком — цепочку промежуточного ПО.
Взаимодействие прокси и повторов
Ротация прокси глубоко связана с движком повторов — стоит запомнить два правила.
1. Бюджет повторов повышается автоматически. При заданном ProxyRotateOnStatus или ProxyRotatePerRequest и более одном прокси в пуле:
Эффективный MaxRetries = max(настроенный MaxRetries, len(ProxyPool) - 1) (предел 10)Например, 5 прокси и MaxRetries = 3: бюджет повышается до 4 (= 5 - 1) — первый запрос идёт через proxy1, после 403 прокси меняются по очереди proxy2…proxy5, каждый опробован по разу.
2. Сбой соединения прокси принуждает к повтору. При активной ротации сбои соединения самого прокси (сбой dial, сбой TLS) обходят обычную классификацию повторяемости и сразу переходят к следующему повтору со следующим прокси — безнадёжные прокси (неверный порт, недостижимый хост) не нужно отсеивать вручную: принудительный повтор плюс размыкание по последовательным сбоям вытеснят их сами.
Условия повторов, математика отката и общий бюджет таймаута на все повторы — в Повторных попытках и отказоустойчивости.
Вопросы безопасности при работе с прокси
Связанные с прокси функции автоматически обрабатывают следующие аспекты безопасности — ручная настройка не требуется:
- Освобождение от SSRF: адреса хостов прокси (
ProxyURLи все записиProxyPool) автоматически добавляются в список освобождений SSRF и не блокируются проверками приватных IP — локальные прокси (например127.0.0.1:7890) тоже работают. - Дедупликация: записи с одинаковыми
host:portавтоматически сливаются, предотвращая перекос ротации и двойной подсчёт. - Проверка URL: все URL прокси проходят проверку безопасности (белый список протоколов http/https/socks5/socks5h, непустой host, защита от инъекций); некорректное значение возвращает ошибку уже в
New(), а не молча игнорируется.
Отношение к TLS: HTTPS-запрос через CONNECT-туннель HTTP-прокси остаётся сквозным TLS — проверка сертификатов, минимальная версия TLS и закрепление сертификатов действуют для целевого сайта как обычно, прокси лишь пересылает шифротекст. При опасении локального загрязнения DNS предпочитайте socks5h (разрешение домена на стороне прокси); при включённом DoH бизнес-домены разрешаются по шифрованному каналу, но сам адрес прокси через DoH не проходит (прокси задаётся разработчиком явно, соединение устанавливается напрямую с хостом прокси). Полная картина защиты — в Защите от SSRF.
Частые проблемы
| Проблема | Причина | Решение |
|---|---|---|
| Прокси не действует | Заданы одновременно ProxyURL и ProxyPool, приоритет у ProxyURL | Очистите ProxyURL, используйте только ProxyPool |
| Исходный IP не меняется при последовательных запросах к одному хосту | Переиспользование соединения закрепило прошлый прокси-туннель | Включите ProxyRotatePerRequest |
| Прокси часто размыкается | ProxyFailureThreshold слишком мал | Увеличьте порог или ProxyCooldown |
| Ротация по статус-коду не срабатывает | Retry.MaxRetries = 0 или в пуле один прокси | Задайте MaxRetries > 0; держите в пуле минимум 2 прокси |
| Что будет, когда разомкнуты все прокси | Последовательные сбои всего пула | Движок резервом вернёт прокси, ближайший к восстановлению, и не отказывает сразу; проверьте доступность прокси и пороги |
| Системный прокси не обнаружен | Нет переменных окружения, платформенные настройки выключены | Проверьте HTTP_PROXY/HTTPS_PROXY или включение системного прокси |
| localhost-прокси блокирован SSRF | Список освобождений системного прокси сканируется один раз при построении | Задайте явно Connection.ProxyURL (всегда освобождён) или настройте SSRFExemptCIDRs |
| Размыкается ли прокси после смены по 403 | Ошибочное ожидание, что статус-код вызывает размыкание | Нет — ротация по статус-коду не размыкает прокси, размыкание вызывают только сбои уровня соединения |
Meta.ProxyURL всегда пусто | Действует только системный прокси — EnableSystemProxy в это поле не записывается | Если нужно наблюдать исходящий адрес, используйте явный ProxyURL или ProxyPool |
Полное описание полей — в Конфигурация API — Пул прокси.
Лучшие практики
| Сценарий | Рекомендуемая настройка |
|---|---|
| Фиксированный корпоративный выход | ProxyURL (http/https/socks5 по необходимости) |
| Следование настройкам системы пользователя | EnableSystemProxy + NO_PROXY для внутренних диапазонов |
| Распределение нагрузки между прокси | ProxyPool + стратегия round-robin по умолчанию |
| Сбор данных с одного хоста | ProxyPool + ProxyRotatePerRequest |
| IP-блокировка CF/WAF | ProxyPool + ProxyRotateOnStatus: []int{403} |
| Неоднородное качество прокси | Увеличьте ProxyFailureThreshold (терпимость к сбоям) и ProxyCooldown |
| Аудит исходного IP | Читайте Result.Meta.ProxyURL, наблюдайте попытки через WithOnResponse |
| Хранение учётных данных прокси | В userinfo URL, логи маскируются автоматически; файл конфигурации храните отдельно |
Цена ротации для производительности
ProxyRotatePerRequest и путь повторов ротации по статус-коду закрывают простаивающие соединения, жертвуя переиспользованием ради смены выхода. Для чувствительных к производительности сценариев без ротации оставьте значения по умолчанию (переиспользование соединений); управление простаивающими соединениями — в Пуле соединений и DNS.
Что дальше
- Пул соединений и DNS - взаимодействие переиспользования соединений и ротации прокси, разрешение DoH
- Повторные попытки и отказоустойчивость - условия повторов, алгоритм отката и связь с пулом прокси
- Оптимизация производительности - цена ротации прокси и комплексная настройка
- Конфигурация API - справочник прокси-полей ConnectionConfig