Как делать асинхронные HTTP-запросы в PHP?

Асинхронный HTTP-запрос помогают нам обрабатывать HTTP-запросы с использованием неблокирующего ввода или вывода в разных потоках. Некоторые называют это возможностями COMET. Основное использование асинхронных HTTP-запросов - это когда клиент запрашивает у сервера поздний ответ. Типичным примером является клиент чата AJAX, в котором мы отправляем или извлекаем данные как с клиента, так и с сервера. Эти сценарии блокируют клиента на длительное время в сокете сервера в ожидании нового сообщения.

PHP обслуживает запросы синхронно. Это означает, что каждая строка кода выполняется синхронно сценария. После получения результата из одной строки он выполняет следующую строку или ожидает результата, прежде чем перейти к выполнению следующей строки кода. Есть случаи, когда мы должны делать запросы к URL-адресам, а они не полагаются друг на друга. В этом сценарии мы не хотим ждать результата одного запроса для выполнения других запросов. Поэтому мы делаем асинхронные запросы.

Библиотека Guzzle 6

Guzzle - это HTTP-клиент на PHP, который помогает отправлять HTTP-запросы. Эти методы могут использоваться для отправки асинхронных HTTP-запросов.

  • RequestAsync,
  • SendAsync,
  • GetAsync,
  • HeadAsync,
  • PutAsync,
  • PostAsync,
  • DeleteAsync,
  • patchAsync

Скачайте пакет guzzle. Его можно установить через composer.

php composer.phar require guzzlehttp/guzzle:~6.0

или

composer require guzzlehttp/guzzle:~6.0

Включите файл autoload.php в часть кода сценария, чтобы он загружал все классы и методы.

<?php
  
require_once(__DIR__. '/vendor/autoload.php');
$client = new GuzzleHttp\Client();
  
$promises = [
    $client->getAsync('http://localhost')
            ->then(function ($response)
    { echo '10'; }),
     
    $client->getAsync('http://www.google.com')
            ->then(function ($response)
    { echo '20'; }),
      
    $client->getAsync('http://localhost')
            ->then(function ($response)
    { echo '30'; }),
      
    $client->getAsync('http://localhost')
            ->then(function ($response)
    { echo '40'; }),
      
    $client->getAsync('http://localhost')
            ->then(function ($response)
    { echo '50'; }),
      
    $client->getAsync('http://localhost')
            ->then(function ($response)
    { echo '60'; }),
      
    $client->getAsync('http://localhost')
            ->then(function ($response)
   { echo '70'; }),
];
  
$results = GuzzleHttp\Promise\unwrap($promises);
  
// Please wait for a while to complete 
// the requests(some of them may fail)
$results = GuzzleHttp\Promise\settle(
        $promises)->wait();
          
print "finish/over." . PHP_EOL;
?>

В приведенный выше код включается файл autoload.php, а затем создается клиентский объект GuzzleHttp, который сохраняется в переменной client, и для каждого запроса Http используется метод getAsync() с URL-адресом.
Запрос, получивший первый ответ, выведет номер. Порядок запроса не имеет значения.

Асинхронные HTTP-запросы с использованием Promise

При использовании Promise результат асинхронной операции представляет собой Promise (специальный объект, который содержит своё состояние). Асинхронные запросы используются для неблокирующих операций HTTP. Когда асинхронные HTTP-запросы отправляют Promise, то возвращается состояние.

Выполним запрос с помощью HTTPlug:

$request = $messageFactory->createRequest(
    'GET', 'http://php-http.org');
$Promise = $client->sendAsyncRequest ($request);
echo 'Неблокирующий!';

«Promise», который возвращается из вышеупомянутого, реализует http\Promise\Promise.

$promise->wait() будет ожидать получения ответа

try {
  $response = $promise->wait();
} catch (\Exception $exception) {
  echo $exception->getMessage();
}  

Вместо ожидания мы можем выполнять шаги асинхронно. Для этого нужно вызвать метод then с двумя аргументами.

  1. Функция обратного вызова, которая будет выполнена, если запрос окажется успешным.
  2. Функция обратного вызова, которая будет выполнена, если запрос приведет к ошибке.
// Success Callback
function (ResponseInterface $response) {
	echo 'New response!';

// Write status code to the log file
	file_put_contents('responses.log', $response->getStatusCode() . "\n", FILE_APPEND);
	return $response;
},

// Failure Callback
function (\Exception $exception) {
	echo ‘We have a problem’;
	throw $exception;
}

Параллелизм в Promise

Параллелизм означает одновременное выполнение нескольких вычислений. Обрабатка большого количества запросов одновременно может улучшить производительность. Для параллелизма мы должны использовать класс EveryPromise и генератор yield и, наконец, добавить wait() в конец программы.

<?php
  
use GuzzleHttp\Promise\EachPromise;
use GuzzleHttp\Psr7\Response;
   
$users = ['one', 'two', 'three'];
   
$promises = (function () use ($users) {
    foreach ($users as $user) {
          
        // Using generator
        yield $this->getAsync(
   'https://api.demo.com/v1/users?username='
        . $user);        
    }
})();
   
$eachPromise = new EachPromise($promises, [
      
    // Number of concurrency
    'concurrency' => 4,
    'fulfilled' => function (Response $response) {
        if ($response->getStatusCode() == 200) {
            $user = json_decode(
                $response->getBody(), true);
              
            // processing response of the user
        }
    },
      
    'rejected' => function ($reason) {
    // handle promise rejected 
    }
]);
   
$eachPromise->promise()->wait();
?>

Многопоточные запросы с cURL

PHP как правило, может обрабатывать несколько запросов. Сначала мы запускаем первый и обрабатываем ответ, затем второй и третий и так далее. Но этот процесс медленный и требует много времени. Но предлагает функции curl_multi_ * для обработки любых asnyc-запросов.

$running = null;
$mh = curl_multi_init();
$ch1 = curl_init();
curl_setopt($ch1, CURLOPT_URL, ‘https://endpoint.com’);

// Other curl options…
curl_multi_add_handle($mh, $ch1);

$ch2 = curl_init();
curl_setopt($ch2, CURLOPT_URL, ‘https://endpoint.com’);

// Other curl options…
curl_multi_add_handle($mh, $ch2);

do {
curl_multi_exec($mh, $running);
curl_multi_select($mh);
} while ($running > 0);

$r1 = curl_multi_getcontent($ch1);
$r2 = curl_multi_getcontent($ch2);
curl_multi_remove_handle($mh, $ch1);
curl_multi_remove_handle($mh, $ch2);
curl_multi_close($mh);

Ответы собираются в переменных «r1» и «r2». С помощью этих функций cURL мы можем запускать запросы параллельно, чтобы сэкономить время и быстрее обрабатывать ответы.