Common.HTTP.ProxyFetch — загрузка URL-контента через прокси
Пакет: Http
Полное имя компонента: Common.Http.ProxyFetch
Что это
ProxyFetch — системный PHP-компонент (V8Wrapper) для загрузки содержимого по HTTP/HTTPS URL внутри сценариев Metabot.
Проще всего воспринимать так:
это аналог file_get_contents($url), но с поддержкой прокси, таймаутов и повторных попыток, на том же cURL-стеке, что используется для надёжных загрузок в платформе (CdnFtp).
Зачем нужен ProxyFetch
file_get_contents()и часть HTTP-клиентов не используют прокси из окружения инстанса → запросы «уходят мимо» SOCKS5/HTTP-прокси и падают или «висят».- Нет единых таймаутов и ретраев → сложнее переживать кратковременные сбои CDN и 5xx.
- Дублировать логику прокси в каждом плагине нецелесообразно.
ProxyFetch даёт одну точку входа для сценариев и плагинов: загрузить строку по URL или получить метаданные (HEAD) с теми же правилами отказоустойчивости.
Где используется
- Загрузка CSV/TXT/JSON по URL перед разбором в сценарии.
- Предварительная проверка размера/доступности файла по URL.
- Замена прямых
file_get_contentsв плагинах вродеCommon.Files.Converter,Common.Integrations.Request(миграция по желанию команды). - Любые кейсы, где бот на сервере обязан ходить в интернет только через прокси.
Где находится компонент
Подключение в JavaScript-сценарии (V8):
const ProxyFetch = require("Common.Http.ProxyFetch")
PHP-класс плагина:
Plugins\Dynamic\Common\Http\V8Wrapper\ProxyFetch
Платформенное ядро (можно вызывать из PHP-плагинов без V8):
App\Services\CdnFtp::fetchUrlContents()App\Services\CdnFtp::getFileInfoByUrl() (расширенная сигнатура с прокси и ретраями)
Как работает ProxyFetch
Архитектура из двух слоёв:
V8 JS → Common.Http.ProxyFetch → mergeWithDefaults (http_outbound + options)
→ CdnFtp::fetchUrlContents / getFileInfoByUrl
→ cURL (прокси, таймауты, ретраи, перебор URL)
Ретраи: отдельные «раунды»; в каждом раунде перебираются варианты URL (href_encode(url) и исходный url). Повтор, если curl_exec дал сбой, http_code === 0 или http_code >= 500.
Логирование: при HTTP_OUTBOUND_LOG_REQUESTS=true в лог пишутся строки вида CdnFtp outbound fetchUrlContents ... / getFileInfoByUrl ....
Что нужно настроить
1. Конфиг config/http_outbound.php
Загружается автоматически; значения берутся из .env.
2. Переменные .env
| Переменная | Назначение |
|---|---|
HTTP_OUTBOUND_PROXY |
Строка прокси (например socks5://login:password@host:port). Пусто — прокси не задаётся. |
HTTP_OUTBOUND_PROXY_VERSION |
Опционально: v4 или v6 (разрешение DNS/IP для cURL). |
HTTP_OUTBOUND_TIMEOUT |
Таймаут запроса, сек (по умолчанию 30). |
HTTP_OUTBOUND_CONNECT_TIMEOUT |
Таймаут на установку соединения, сек (по умолчанию 10). |
HTTP_OUTBOUND_RETRIES |
Число раундов попыток (по умолчанию 3). |
HTTP_OUTBOUND_RETRY_AFTER_SEC |
Пауза между раундами, сек (по умолчанию 2). |
HTTP_OUTBOUND_LOG_REQUESTS |
true/false — писать диагностические строки в лог (по умолчанию true). |
Важно: эта группа независима от telegram и других каналов. Если на проекте один прокси на всё, можно задать одинаковые значения в обеих группах; если прокси разные — настраивайте раздельно.
3. Установка плагина на инстансе
См. раздел «Создание плагина в админке» ниже и спеку specs/Task-9703-proxy-fetch-plugin.plan.md.
После деплоя класса из Git выполните:
composer dump-autoload -o
Создание плагина в админке
Если плагин не поставляется из репозитория, создайте его вручную:
- Плагин: имя каталога/пакета
Http, тип стандартный, Common, уровень доступа SYSTEM. - Скрипт: имя
ProxyFetch, тип PHP (WRAPPER FOR V8). - Вставьте код из файла
Plugins/Dynamic/Common/Http/V8Wrapper/ProxyFetch.php(namespace и имя класса должны совпадать). - Включите скрипт (is_enabled).
Платформа сохранит PHP-файл в структуру динамических плагинов; при расхождении путей с PSR-4 уточните у команды, как на вашем инстансе подключается ModuleLoader.
Сигнатура вызова (JavaScript / V8)
getContents
const ProxyFetch = require("Common.Http.ProxyFetch")
let result = ProxyFetch.getContents("https://example.com/data.csv")
if (!result.ok) {
debug("Ошибка загрузки: " + result.last_curl_error)
return false
}
let text = result.content
С переопределением параметров:
let result = ProxyFetch.getContents("https://example.com/big.bin", {
proxy: "socks5://user:pass@proxy.example:1080",
timeout: 120,
connect_timeout: 15,
max_attempts: 5,
retry_after_sec: 3
})
getFileInfo
let info = ProxyFetch.getFileInfo("https://example.com/photo.jpg")
if (info.exists && info.size_kb < 20480) {
// условно безопасный размер
}
Параметры options
| Поле | Тип | Обязателен | Описание |
|---|---|---|---|
proxy |
string | Нет | Переопределить прокси для этого вызова |
timeout |
number | Нет | Таймаут cURL, сек (дефолт из http_outbound.timeout) |
connect_timeout |
number | Нет | Таймаут соединения, сек |
max_attempts |
number | Нет | Число раундов ретраев (http_outbound.retries) |
retry_after_sec |
number | Нет | Пауза между раундами |
Формат ответа getContents
| Поле | Тип | Описание |
|---|---|---|
ok |
boolean | Успешная доставка ответа: есть тело и http_code > 0 |
content |
string | null | Тело ответа; null при провале |
http_code |
number | HTTP-код последнего ответа |
attempts |
number | Сколько отдельных cURL-запросов было выполнено |
last_curl_error |
string | Текст ошибки при ok === false; при успехе — пустая строка |
Про 404: при ответе 404 платформа может вернуть ok === true и тело страницы ошибки — всегда проверяйте http_code, если вам нужен именно успешный ресурс.
Формат ответа getFileInfo
Наследует прежние поля:
exists(0/1) — по сути «код ответа 200».size_kb,type,mime_type,name.
Дополнительно (удобно для отладки):
http_codelast_curl_errorattempts
Использование из PHP (без V8)
use App\Services\CdnFtp;
$result = CdnFtp::fetchUrlContents(
$url,
config('http_outbound.proxy'),
(int) config('http_outbound.timeout', 30),
(int) config('http_outbound.connect_timeout', 10),
(int) config('http_outbound.retries', 3),
(int) config('http_outbound.retry_after_sec', 2)
);
if (!$result['ok']) {
// логировать $result['last_curl_error']
}
HEAD / метаданные:
$info = CdnFtp::getFileInfoByUrl(
$url,
config('http_outbound.proxy'),
(int) config('http_outbound.connect_timeout', 10),
(int) config('http_outbound.timeout', 30),
(int) config('http_outbound.retries', 3),
(int) config('http_outbound.retry_after_sec', 2)
);
Одноаргументный вызов CdnFtp::getFileInfoByUrl($url) сохраняет старое поведение (без таймаутов из http_outbound).
Пример замены file_get_contents в плагине
Было:
$data = file_get_contents($url);
Стало (через ядро):
$fetch = \App\Services\CdnFtp::fetchUrlContents(
$url,
config('http_outbound.proxy'),
(int) config('http_outbound.timeout', 30),
(int) config('http_outbound.connect_timeout', 10),
(int) config('http_outbound.retries', 3),
(int) config('http_outbound.retry_after_sec', 2)
);
if (!$fetch['ok']) {
throw new \RuntimeException($fetch['last_curl_error'] ?: 'fetch failed');
}
$data = $fetch['content'];
Обработка ошибок
| Ситуация | Что ожидать |
|---|---|
| Таймаут | ok === false, в last_curl_error часто cURL error 28: ... |
| Прокси недоступен | ok === false, типично cURL error 7: ... |
| 5xx после ретраев | ok === false, last_curl_error может содержать HTTP 503 |
| 404 | Обычно ok === true, http_code === 404 — проверяйте код |
| Невалидный URL / пустая строка | ok === false, last_curl_error поясняет причину |
Сетевую ошибку и «битый» контент (HTML вместо CSV) различайте по http_code и валидации содержимого в сценарии.
Как отлаживать
.env— заданы лиHTTP_OUTBOUND_PROXY, таймауты и ретраи.- Ручной
curlс того же сервера (с тем же прокси, если нужен) — доступен ли URL. - Логи приложения — строки
CdnFtp outbound ...при включённомHTTP_OUTBOUND_LOG_REQUESTS. result.last_curl_error/info.last_curl_error— конкретная причина отказа.http_code— отличить 404/403 от обрыва соединения.attempts— сколько запросов реально выполнено (ретраи работают).
FAQ
Можно ли задать другой прокси только для одного вызова?
Да, передайте proxy в объекте options.
Как отключить ретраи?max_attempts: 1 в options или HTTP_OUTBOUND_RETRIES=1.
Чем отличается от bot.downloadFileFromUrl()?
Скачивание в сценарий бота кладёт файл в хранилище бизнеса. ProxyFetch / fetchUrlContents возвращают строку в памяти, без сохранения на диск платформы.
Работает ли без прокси?
Да: если HTTP_OUTBOUND_PROXY пуст и в options не передан proxy, используется прямой cURL.
Нужен ли отдельный токен или callback?
Нет, это синхронный HTTP-запрос из PHP, без webhook’ов.
Связанные материалы
- Спека:
specs/Task-9703-proxy-fetch-plugin.plan.md - Код:
app/Services/CdnFtp.php,Plugins/Dynamic/Common/Http/V8Wrapper/ProxyFetch.php,config/http_outbound.php
Нет комментариев