Возможно ли выполнение асинхронных HTTP-запросов с помощью PHP?

У меня есть сценарий PHP, который должен загрузить несколько файлов с удаленного сервера. На данный момент у меня просто есть цикл загрузки и обработки файлов с помощью cURL, что означает, что он не начинает загружать один файл, пока не завершится предыдущий - это значительно увеличивает время выполнения скрипта.

Можно ли запустить несколько экземпляров cURL, например, для асинхронной загрузки этих файлов одновременно, не дожидаясь завершения предыдущего? Если так, как это будет достигнуто?

5 ответов

Решение

Да.

Существует многозапрошенная PHP-библиотека (или см.: архивированный проект Google Code). Это многопоточная библиотека CURL.

В качестве другого решения вы можете написать скрипт, который делает это на языке, поддерживающем многопоточность, таком как Ruby или Python. Затем просто вызовите скрипт с помощью PHP. Кажется довольно просто.

Проверить скручиваемость легко. Он поддерживает как блокирующие, так и неблокирующие запросы одновременно или один запрос. Кроме того, он тестируется модульно, в отличие от многих простых или с ошибками библиотек.

Раскрытие: я являюсь автором этой библиотеки. У библиотеки есть собственный набор тестов, поэтому я уверен, что он надежный.

Также, посмотрите пример использования ниже:

<?php
// We will download info about 2 YouTube videos:
// http://youtu.be/XmSdTa9kaiQ and
// http://youtu.be/6dC-sm5SWiU

// Init queue of requests
$queue = new cURL\RequestsQueue;
// Set default options for all requests in queue
$queue->getDefaultOptions()
    ->set(CURLOPT_TIMEOUT, 5)
    ->set(CURLOPT_RETURNTRANSFER, true);
// Set callback function to be executed when request will be completed
$queue->addListener('complete', function (cURL\Event $event) {
    $response = $event->response;
    $json = $response->getContent(); // Returns content of response
    $feed = json_decode($json, true);
    echo $feed['entry']['title']['$t'] . "\n";
});

$request = new cURL\Request('http://gdata.youtube.com/feeds/api/videos/XmSdTa9kaiQ?v=2&alt=json');
$queue->attach($request);

$request = new cURL\Request('http://gdata.youtube.com/feeds/api/videos/6dC-sm5SWiU?v=2&alt=json');
$queue->attach($request);

// Execute queue
$queue->send();

Для PHP5.5 +, mpyw / co является окончательным решением. Это работает, как будто это TJ / Co в JavaScript.

пример

Предположим, что вы хотите загрузить указанные аватары нескольких пользователей GitHub. Следующие шаги необходимы для каждого пользователя.

  1. Получить содержание http://github.com/mpyw (GET HTML)
  2. найти <img class="avatar" src="..."> и запросить его (ПОЛУЧИТЬ ИЗОБРАЖЕНИЕ)

---: Жду моего ответа
...: Ожидание другого ответа в параллельных потоках

Многие известные curl_multi основанные сценарии уже предоставляют нам следующие потоки.

        /-----------GET HTML\  /--GET IMAGE.........\
       /                     \/                      \ 
[Start] GET HTML..............----------------GET IMAGE [Finish]
       \                     /\                      /
        \-----GET HTML....../  \-----GET IMAGE....../

Однако это недостаточно эффективно. Хотите сократить время ожидания? ...?

        /-----------GET HTML--GET IMAGE\
       /                                \            
[Start] GET HTML----------------GET IMAGE [Finish]
       \                                /
        \-----GET HTML-----GET IMAGE.../

Да, это очень легко с mpyw/co. Для получения более подробной информации посетите страницу хранилища.

Библиотека @stil такая классная. Большое ему спасибо!

Тем не менее, я написал замечательную служебную функцию, которая позволяет очень легко получать асинхронный контент с нескольких URL-адресов (в моем случае это API-интерфейсы) и возвращать их без потери информации, которая есть какая.

Вы просто запускаете его, передавая ключ => значение массива в качестве входных данных, и он возвращает ключ => ответный массив как результат:-)

/** 
     * This function runs multiple GET requests parallely.<br />
     * @param array $urlsArray needs to be in format:<br />
     * <i>array(<br />
     * [url_unique_id_1] => [url_for_request_1],<br />
     * [url_unique_id_2] => [url_for_request_2],<br />
     * [url_unique_id_3] => [url_for_request_3]<br />
     * )</i><br />
     * e.g. input like:<br />
     *  <i>array(<br />
     * &nbsp; "myemail@gmail.com" =>
     * &nbsp; "http://someapi.com/results?search=easylife",<br />
     * &nbsp; "michael@gmail.com" =>
     * &nbsp; "http://someapi.com/results?search=safelife"<br />
     * )</i>
     * @return array An array where for every <i>url_unique_id</i> response to this request 
     * is returned e.g.<br />
     * <i>array(<br />
     * &nbsp; "myemail@gmail.com" => <br />
     * &nbsp; "Work less, enjoy more",<br />
     * &nbsp; "michael@gmail.com" => <br />
     * &nbsp; "Study, work, pay taxes"<br />
     * )</i>
     *  */
    public function getResponsesFromUrlsAsynchronously(array $urlsArray, $timeout = 8) {
        $queue = new \cURL\RequestsQueue;

        // Set default options for all requests in queue
        $queue->getDefaultOptions()
                ->set(CURLOPT_TIMEOUT, $timeout)
                ->set(CURLOPT_RETURNTRANSFER, true);

        // =========================================================================
        // Define some extra variables to be used in callback

        global $requestUidToUserUrlIdentifiers;
        $requestUidToUserUrlIdentifiers = array();

        global $userIdentifiersToResponses;
        $userIdentifiersToResponses = array();

        // =========================================================================

        // Set function to be executed when request will be completed
        $queue->addListener('complete', function (\cURL\Event $event) {

            // Define user identifier for this url
            global $requestUidToUserUrlIdentifiers;
            $requestId = $event->request->getUID();
            $userIdentifier = $requestUidToUserUrlIdentifiers[$requestId];

            // =========================================================================

            $response = $event->response;
            $json = $response->getContent(); // Returns content of response

            $apiResponseAsArray = json_decode($json, true);
            $apiResponseAsArray = $apiResponseAsArray['jobs'];

            // =========================================================================
            // Store this response in proper structure
            global $userIdentifiersToResponses;
            $userIdentifiersToResponses[$userIdentifier] = $apiResponseAsArray;
        });

        // =========================================================================

        // Add all request to queue
        foreach ($urlsArray as $userUrlIdentifier => $url) {
            $request = new \cURL\Request($url);
            $requestUidToUserUrlIdentifiers[$request->getUID()] = $userUrlIdentifier;
            $queue->attach($request);
        }

        // =========================================================================

        // Execute queue
        $queue->send();

        // =========================================================================

        return $userIdentifiersToResponses;
    }

В PHP 7.0 и Apache 2.0, как сказано в PHP exec Document, перенаправление вывода, добавив " &> / dev / null &" в конце команды, может сделать его работающим в фоновом режиме, просто не забудьте правильно обернуть команду,

$time = microtime(true);
$command = '/usr/bin/curl -H \'Content-Type: application/json\' -d \'' . $curlPost . '\' --url \'' . $wholeUrl . '\' >> /dev/shm/request.log 2> /dev/null &';
exec($command);
echo (microtime(true) - $time) * 1000 . ' ms';

Выше работает хорошо для меня, занимает всего 3 мс, но следующий не будет работать, занимает 1500 мс.

$time = microtime(true);
$command = '/usr/bin/curl -H \'Content-Type: application/json\' -d \'' . $curlPost . '\' --url ' . $wholeUrl;
exec($command . ' >> /dev/shm/request.log 2> /dev/null &');
echo (microtime(true) - $time) * 1000 . ' ms';

В целом, добавление " &> /dev/null &" в конце вашей команды может помочь, просто не забывайте правильно оборачивать вашу команду.

Другие вопросы по тегам