PHP и cURL: настройка таймаутов

Раздел: PHP -> HTTP запросы с 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 секунд может не хватить. В таких случаях таймаут соединения должен быть больше, либо использовать асинхронные запросы.

- Curl setopt php (php curl setopt)
- Php curl ssl (php curl ssl)
- Php curl url (php curl url)

Дополнительные практические примеры с более сложными сценариями использования таймаутов.

Пример 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

PHP cURL таймаут - comments

En
Php curl timeout (php)