Подтвердить что ты не робот

PHP file_get_contents очень медленный при использовании полного URL-адреса

Я работаю с script (который я не создавал изначально), который создает файл PDF с HTML-страницы. Проблема в том, что сейчас требуется очень много времени, например, 1-2 минуты. Предположительно, это работало нормально, но замедлилось в течение последних нескольких недель.

script вызывает file_get_contents на PHP скрипт, который затем выводит результат в HTML файл на сервере и запускает приложение-генератор pdf в этом файле.

Я, кажется, сузил проблему до вызова file_get_contents на полный URL-адрес, а не на локальный путь.

Когда я использую

$content = file_get_contents('test.txt');

он обрабатывается почти мгновенно. Однако, если я использую полный URL

$content = file_get_contents('http://example.com/test.txt');

для обработки требуется от 30 до 90 секунд.

Он не ограничен нашим сервером, он медленен при доступе к любому внешнему URL-адресу, например http://www.google.com. Я полагаю, что script вызывает полный URL-адрес, поскольку требуются строковые переменные запроса, которые не работают, если вы вызываете файл локально.

Я также пробовал fopen, readfile и curl, и все они были медленными. Любые идеи о том, где искать, чтобы исправить это?

4b9b3361

Ответ 1

Примечание: Это было исправлено в PHP 5.6.14. Заголовок Connection: close теперь будет автоматически отправляться даже для запросов HTTP/1.0. См. commit 4b1dff6.

Мне было трудно выяснить причину медленности скриптов file_get_contents.

Анализируя это с помощью Wireshark, проблема (в моем случае и, вероятно, ваша тоже) заключалась в том, что удаленный веб-сервер НЕ ЗАКРЫВАЕТ СОЕДИНЕНИЕ TCP ДО 15 СЕКУНД (т.е. "keep-alive" ).

Действительно, file_get_contents не отправляет HTTP-заголовок "соединение", поэтому удаленный веб-сервер по умолчанию считает, что он поддерживает соединение и не закрывает поток TCP до 15 секунд (это может быть не стандартное value - зависит от сервера conf).

Обычный браузер считает, что страница полностью загружена, если длина полезной нагрузки HTTP достигает длины, указанной в ответе HTTP-заголовка Content-Length. File_get_contents не делает этого и это позор.

Решение

SO, если вы хотите узнать решение, вот оно:

$context = stream_context_create(array('http' => array('header'=>'Connection: close\r\n')));
file_get_contents("http://www.something.com/somepage.html",false,$context);

Дело в том, чтобы сообщить удаленному веб-серверу закрыть соединение при завершении загрузки, так как file_get_contents недостаточно интеллектуальны, чтобы сделать это самостоятельно, используя ответ HTTP-заголовка Content-Length.

Ответ 2

Я бы использовал curl() для извлечения внешнего содержимого, так как это намного быстрее, чем метод file_get_contents. Не уверен, что это решит проблему, но стоит сделать снимок.

Также обратите внимание, что скорость вашего сервера будет влиять на время, необходимое для извлечения файла.

Вот пример использования:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://example.com/test.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);

Ответ 3

Иногда, поскольку DNS слишком медленный на вашем сервере, попробуйте следующее:

заменить

echo file_get_contents('http://www.google.com');

а

$context=stream_context_create(array('http' => array('header'=>"Host: www.google.com\r\n")));
echo file_get_contents('http://74.125.71.103', false, $context);

Ответ 4

У меня была такая же проблема,

Единственное, что сработало для меня, - это установить тайм-аут в массиве $options.

$options = array(
    'http' => array(
        'header'  => implode($headers, "\r\n"),
        'method'  => 'POST',
        'content' => '',
        'timeout' => .5
    ),
);

Ответ 5

Можете ли вы попытаться извлечь этот URL-адрес на сервере из командной строки? завиток или wget приходят на ум. Если они получают URL с нормальной скоростью, то это не сетевая проблема и, скорее всего, что-то в настройке apache/php.

Ответ 6

$context = stream_context_create(array('http' => array('header'=>'Connection: close\r\n')));
$string = file_get_contents("http://localhost/testcall/request.php",false,$context);

Время: 50976 мс (время avaerage всего 5 попыток)

$ch = curl_init();
$timeout = 5;
curl_setopt($ch, CURLOPT_URL, "http://localhost/testcall/request.php");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
echo $data = curl_exec($ch);
curl_close($ch);

Время: 46679 мс (время avaerage всего 5 попыток)

Примечание. request.php используется для извлечения некоторых данных из базы данных mysql.

Ответ 7

У меня есть огромные данные, переданные API, я использую file_get_contents для чтения данных, но это заняло около 60 секунд. Однако, используя решение KrisWebDev, потребовалось около 25 секунд.

$context = stream_context_create(array('https' => array('header'=>'Connection: close\r\n')));
file_get_contents($url,false,$context);

Ответ 8

То, что я также рассмотрел бы с Curl, это то, что вы можете "потопить" запросы. Это очень помогло мне, так как у меня нет доступа к версии PHP, которая позволяет нарезать резьбу на данный момент.

Например, я получал 7 изображений с удаленного сервера, используя file_get_contents, и он занимал 2-5 секунд за запрос. Только этот процесс включал 30 секунд или что-то в этом процессе, в то время как пользователь ждал создания PDF файла.

Это буквально сократило время до примерно 1 изображения. Другой пример: я проверяю 36 URL-адресов за время, которое потребовалось ранее, чтобы сделать это. Я думаю, вы понимаете.: -)

    $timeout = 30;
    $retTxfr = 1;
    $user = '';
    $pass = '';

    $master = curl_multi_init();
    $node_count = count($curlList);
    $keys = array("url");

    for ($i = 0; $i < $node_count; $i++) {
        foreach ($keys as $key) {
            if (empty($curlList[$i][$key])) continue;
            $ch[$i][$key] = curl_init($curlList[$i][$key]);
            curl_setopt($ch[$i][$key], CURLOPT_TIMEOUT, $timeout); // -- timeout after X seconds
            curl_setopt($ch[$i][$key], CURLOPT_RETURNTRANSFER, $retTxfr);
            curl_setopt($ch[$i][$key], CURLOPT_HTTPAUTH, CURLAUTH_ANY);
            curl_setopt($ch[$i][$key], CURLOPT_USERPWD, "{$user}:{$pass}");
            curl_setopt($ch[$i][$key], CURLOPT_RETURNTRANSFER, true);
            curl_multi_add_handle($master, $ch[$i][$key]);
        }
    }

    // -- get all requests at once, finish when done or timeout met --
    do {  curl_multi_exec($master, $running);  }
    while ($running > 0);

Затем проверьте результаты:

            if ((int)curl_getinfo($ch[$i][$key], CURLINFO_HTTP_CODE) > 399 || empty($results[$i][$key])) {
                unset($results[$i][$key]);
            } else {
                $results[$i]["options"] = $curlList[$i]["options"];
            }
            curl_multi_remove_handle($master, $ch[$i][$key]);
            curl_close($ch[$i][$key]);

затем закройте файл:

    curl_multi_close($master);

Ответ 9

Я знаю, что это старый вопрос, но я нашел его сегодня, и ответы не сработали для меня. Я не видел, чтобы кто-то говорил, что максимальное количество соединений на IP может быть установлено равным 1. Таким образом, вы выполняете API-запрос, а API выполняет другой запрос, потому что вы используете полный URL-адрес. Вот почему загрузка непосредственно с диска работает. Для меня это устранило проблему:

if (strpos($file->url, env('APP_URL')) === 0) {
    $url = substr($file->url, strlen(env('APP_URL')));
} else {
    $url = $file->url;
}
return file_get_contents($url);