PHP и cURL: настройка таймаутов
Настройка таймаутов в PHP cURL
При выполнении HTTP запросов через cURL важно контролировать время ожидания ответа. Отсутствие таймаута может привести к зависанию скрипта. Рассмотрим основные способы управления временем ожидания.
Основное решение: CURLOPT_TIMEOUT и CURLOPT_CONNECTTIMEOUT
Самый надёжный способ - использовать опции CURLOPT_TIMEOUT (максимальное время выполнения всего запроса) и CURLOPT_CONNECTTIMEOUT (время установки соединения). Обе опции указываются в секундах.
$ch = curl_init('https://example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // таймаут соединения 10 сек
curl_setopt($ch, CURLOPT_TIMEOUT, 30); // общий таймаут 30 сек
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Ошибка: ' . curl_error($ch);
}
curl_close($ch);Php curl close (php curl close)
Эти опции работают во всех версиях cURL. Комбинирование двух таймаутов позволяет отделить проблемы с сетью от проблем с обработкой данных на сервере.
Типичная ошибка: значение CURLOPT_TIMEOUT меньше CURLOPT_CONNECTTIMEOUT. В этом случае после успешного соединения запрос может быть прерван слишком рано. Рекомендуется всегда задавать CURLOPT_TIMEOUT больше, чем CURLOPT_CONNECTTIMEOUT.
Как установить таймаут в миллисекундах?
Для более точного контроля в современных версиях cURL (сборки с поддержкой) можно использовать CURLOPT_TIMEOUT_MS и CURLOPT_CONNECTTIMEOUT_MS. Значения задаются в миллисекундах.
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 500); // 0.5 сек
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 2000); // 2 сек
$data = curl_exec($ch);
if (curl_errno($ch) === CURLE_OPERATION_TIMEDOUT) {
echo 'Таймаут запроса';
}
curl_close($ch);Php curl cookie (php curl cookie)
На некоторых платформах (Windows, старые сборки) миллисекундные таймауты могут игнорироваться и округляться до секунд. Это стоит проверять.
Проблема: если сервер не поддерживает миллисекундные таймауты, cURL может вернуть ошибку CURLE_BAD_FUNCTION_ARGUMENT. Решение - либо обновить cURL, либо использовать секундные варианты.
Как влияет max_execution_time на таймаут cURL?
В PHP есть директива max_execution_time, которая ограничивает время выполнения всего скрипта. Если она установлена меньше, чем таймаут cURL, скрипт может быть прерван раньше, чем cURL дождётся ответа.
ini_set('max_execution_time', 120); // разрешить скрипту работать до 120 секунд
$ch = curl_init('https://slow.example.com');
curl_setopt($ch, CURLOPT_TIMEOUT, 60); // таймаут cURL 60 секунд
// ... выполнение запросаCurl exec php (php curl exec)
Важно устанавливать max_execution_time со значением, превышающим максимальный таймаут cURL. Иначе PHP убьёт скрипт до завершения запроса.
Ошибка: игнорирование max_execution_time при запуске из командной строки (CLI) - по умолчанию он равен 0 (нет ограничений). Но в веб-окружении (mod_php, FastCGI) ограничения могут быть жёсткими.
Как контролировать таймауты при перенаправлениях (редиректах)?
Когда включена опция CURLOPT_FOLLOWLOCATION, cURL автоматически следует за редиректами. Общий таймаут CURLOPT_TIMEOUT при этом распространяется на все переходы, а не на каждый запрос отдельно.
$ch = curl_init('https://bit.ly/something');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // общий таймаут 10 секунд на всю цепочку
curl_exec($ch);Php curl get (php curl get запрос)
Если нужно задать отдельный таймаут на каждый редирект, придётся вручную обрабатывать заголовки Location.
Проблема: использование CURLOPT_TIMEOUT в сочетании с CURLOPT_FOLLOWLOCATION может привести к преждевременному завершению, если сервер долго отвечает на редирект. Рекомендуется увеличивать таймаут с учётом количества переходов.
Как обработать ошибку таймаута в коде?
При наступлении таймаута cURL возвращает код ошибки CURLE_OPERATION_TIMEDOUT (28). Правильная обработка позволяет логировать событие и повторять запрос.
$ch = curl_init('https://unreliable.example.com');
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
$response = curl_exec($ch);
$error = curl_error($ch);
$errno = curl_errno($ch);
curl_close($ch);
if ($errno === CURLE_OPERATION_TIMEDOUT) {
// запись в лог, повторный запрос или возврат ошибки
echo 'Таймаут. Повтор через 1 сек...';
sleep(1);
// возможно, повторный curl_exec с новым handle
} else {
echo 'Успех';
}Php curl html (php curl html)
Не следует путать таймаут с другими ошибками, например, с невозможностью разрешить DNS (CURLE_COULDNT_RESOLVE_HOST).
Распространённая ошибка: попытка повторно использовать дескриптор cURL после таймаута без полного сброса. Лучше создавать новый handle через curl_init().
Как ограничить время DNS разрешения?
cURL не предоставляет прямого таймаута на DNS, но можно использовать системный CURLOPT_DNS_CACHE_TIMEOUT (время кэширования DNS) и самостоятельно задать таймаут через конфигурацию стека. Альтернативный подход - задать IP и порт напрямую через CURLOPT_RESOLVE.
$ch = curl_init('https://example.com');
curl_setopt($ch, CURLOPT_DNS_CACHE_TIMEOUT, 120); // кэшировать DNS на 2 минуты
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); // общий таймаут соединения
curl_exec($ch);Если DNS-запрос зависает, поможет только общий CURLOPT_CONNECTTIMEOUT, так как он включает время резолвинга.
Проблема: на медленных DNS-серверах даже при CURLOPT_CONNECTTIMEOUT в 5 секунд может не хватить. В таких случаях таймаут соединения должен быть больше, либо использовать асинхронные запросы.
Дополнительные практические примеры с более сложными сценариями использования таймаутов.
Пример 1: Комбинированный таймаут с обработкой ошибок и повторным запросом
function fetchUrlWithRetry($url, $timeout = 10, $retries = 3) {
$attempt = 0;
while ($attempt < $retries) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => $timeout,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
]);
$result = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
curl_close($ch);
if ($errno === CURLE_OPERATION_TIMEDOUT) {
$attempt++;
if ($attempt < $retries) {
sleep(1); // пауза перед повторной попыткой
continue;
}
return ['error' => 'Таймаут после ' . $retries . ' попыток'];
}
if ($errno) {
return ['error' => 'Ошибка cURL: ' . $error];
}
return ['data' => $result];
}
}
$result = fetchUrlWithRetry('https://slow.example.com', 5, 3);
print_r($result);Array
(
[data] => (или [error] => Таймаут после 3 попыток)
)Пример 2: Использование таймаута в миллисекундах и проверка поддержки
$ch = curl_init('https://httpbin.org/delay/3');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT_MS => 1000,
CURLOPT_TIMEOUT_MS => 1500,
]);
$start = microtime(true);
$result = curl_exec($ch);
$time = (microtime(true) - $start) * 1000;
$errno = curl_errno($ch);
curl_close($ch);
echo 'Время выполнения: ' . round($time, 0) . ' ms\n';
echo 'Код ошибки: ' . $errno;
echo ($errno === CURLE_OPERATION_TIMEDOUT) ? ' - Таймаут' : ' - Успех';Время выполнения: 1520 ms Код ошибки: 28 - Таймаут
Пример 3: Таймаут в асинхронных запросах (curl_multi)
$urls = ['https://example.com', 'https://httpbin.org/delay/2', 'https://google.com'];
$mh = curl_multi_init();
$handles = [];
foreach ($urls as $i => $url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_CONNECTTIMEOUT => 2,
]);
curl_multi_add_handle($mh, $ch);
$handles[$i] = $ch;
}
$active = null;
do {
$status = curl_multi_exec($mh, $active);
} while ($status === CURLM_CALL_MULTI_PERFORM || $active);
foreach ($handles as $i => $ch) {
$error = curl_error($ch);
$data = curl_multi_getcontent($ch);
echo "$i: " . ($error ?: 'OK') . "\n";
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);0: OK 1: Operation timed out after 3000 milliseconds with 0 bytes received 2: OK
Пример 4: Загрузка файла с таймаутом и прогрессом
$url = 'https://speed.hetzner.de/100MB.bin';
$ch = curl_init($url);
$fp = fopen('/dev/null', 'w');
curl_setopt_array($ch, [
CURLOPT_FILE => $fp,
CURLOPT_TIMEOUT => 10,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_NOPROGRESS => false,
CURLOPT_PROGRESSFUNCTION => function($ch, $dltotal, $dlnow) {
static $last = 0;
if (time() - $last > 2) {
echo 'Progress: ' . round($dlnow / $dltotal * 100, 2) . "%\n";
$last = time();
}
},
]);
$start = microtime(true);
curl_exec($ch);
$time = microtime(true) - $start;
$errno = curl_errno($ch);
curl_close($ch);
fclose($fp);
echo 'Завершено за ' . round($time, 2) . ' сек, код: ' . $errno;Progress: 12.5% Progress: 38.2% Operation timed out after 10000 milliseconds with 0 bytes received Завершено за 10.01 сек, код: 28