Как выполнять одновременные запросы с помощью GuzzleHttp

Как использовать Guzzle 6 для создания 5 асинхронных запросов со следующими условиями:

  • Все запросы начинаются одновременно
  • Я хочу значение тайм-аута 500 мс для всех запросов. Если время запроса истекло, я НЕ хочу, чтобы он прерывал другие запросы
  • Если запрос возвращает не-200, я НЕ хочу, чтобы он прерывал другие запросы.
  • Все запросы находятся в разных доменах... (поэтому я не уверен, как это согласуется с настройкой base_uri...

Если все 5 запросов возвращают 200OK ‹ 500 мс, я хочу иметь возможность перебирать их ответы...

НО, если, скажем, у 2 из них не 200, а у 1 из них истекает время ожидания (более 500 мс), я хочу по-прежнему иметь доступ к ответам для 2 успешных.

EDIT Пока все работает, за исключением того, что тайм-ауты все еще вызывают исключение.

Вот что у меня было до сих пор:

<?php

  require __DIR__.'/../vendor/autoload.php';

  use GuzzleHttp\Client;
  use GuzzleHttp\Promise;

  $client = new Client([
    'http_errors'     => false,
    'connect_timeout' => 1.50, //////////////// 0.50
    'timeout'         => 2.00, //////////////// 1.00
    'headers' => [
      'User-Agent' => 'Test/1.0'
    ]
  ]);

  // initiate each request but do not block
  $promises = [
    'success'            => $client->getAsync('https://httpbin.org/get'),
    'success'            => $client->getAsync('https://httpbin.org/delay/1'),
    'failconnecttimeout' => $client->getAsync('https://httpbin.org/delay/2'),
    'fail500'            => $client->getAsync('https://httpbin.org/status/500'),
  ];

  // wait on all of the requests to complete. Throws a ConnectException if any
  // of the requests fail
  $results = Promise\unwrap($promises);

  // wait for the requests to complete, even if some of them fail
  $results = Promise\settle($promises)->wait();

person Tallboy    schedule 12.11.2018    source источник
comment
Вы смотрели ответ на stackoverflow или документ Guzzle? Если запросы выполняются успешно, возвращается массив из Guzzle\Http\Message\Response объектов. Сбой одного запроса не приведет к сбою всего пула запросов. Любые исключения, возникающие при передаче пула запросов, будут объединены в исключение Guzzle\Common\Exception\MultiTransferException. Это комментарий из их документа.   -  person moyeen52    schedule 13.11.2018
comment
Да, я просто пытаюсь сделать так, чтобы исключения не выбрасывались, а другие запросы могли продолжаться как обычно. я собираюсь обновить свой код последней версией, которая у меня есть   -  person Tallboy    schedule 13.11.2018
comment
Спасибо, я плохо посмотрю на этот ответ, хотя я не часто использую PHP, так что это немного головная боль.   -  person Tallboy    schedule 13.11.2018
comment
Итак, в настоящее время, если какой-либо из 5 запросов завершается с ошибкой 500, весь ответ завершается ошибкой. как мне сделать так, чтобы каждый запрос автоматически терпел неудачу, чтобы я мог просто вернуть 200 OK с успешными ответами?   -  person Tallboy    schedule 13.11.2018
comment
Итак, у меня все работает с 'http_errors' => false... единственное, что осталось, это то, что тайм-ауты все еще вызывают исключение   -  person Tallboy    schedule 13.11.2018


Ответы (1)


Guzzle предоставляет fulfilled и rejected callabcks в пуле. здесь я выполнил тест по вашим значениям, подробнее читайте в Guzzle docs:

    $client = new Client([
        'http_errors'     => false,
        'connect_timeout' => 0.50, //////////////// 0.50
        'timeout'         => 1.00, //////////////// 1.00
        'headers' => [
          'User-Agent' => 'Test/1.0'
        ]
      ]);

$requests = function ($total) {
    $uris = [
        'https://httpbin.org/get',
        'https://httpbin.org/delay/1',
        'https://httpbin.org/delay/2',
        'https://httpbin.org/status/500',
        ];
    for ($i = 0; $i < count($uris); $i++) {
        yield new Request('GET', $uris[$i]);
    }
};

$pool = new Pool($client, $requests(8), [
    'concurrency' => 10,
    'fulfilled' => function ($response, $index) {
        // this is delivered each successful response
        print_r($index."fulfilled\n");
    },
    'rejected' => function ($reason, $index) {
        // this is delivered each failed request
        print_r($index."rejected\n");
    },
]);
// Initiate the transfers and create a promise
$promise = $pool->promise();
// Force the pool of requests to complete.
$promise->wait();

отклик

0fulfilled
3fulfilled
1rejected
2rejected

если вы хотите использовать свой код выше, вы также можете передать статус ответа в своих обещаниях $, вот пример:

use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Exception\RequestException;
....
$client = new Client([
    'http_errors'     => false,
    'connect_timeout' => 1.50, //////////////// 0.50
    'timeout'         => 2.00, //////////////// 1.00
    'headers' => [
      'User-Agent' => 'Test/1.0'
    ]
  ]);

            $promises = [
        'success' => $client->getAsync('https://httpbin.org/get')->then(
            function (ResponseInterface $res) {
                echo $res->getStatusCode() . "\n";
            },
            function (RequestException $e) {
                echo $e->getMessage() . "\n";
                echo $e->getRequest()->getMethod();
            }
        )
        ,
        'success' => $client->getAsync('https://httpbin.org/delay/1')->then(
            function (ResponseInterface $res) {
                echo $res->getStatusCode() . "\n";
            },
            function (RequestException $e) {
                echo $e->getMessage() . "\n";
                echo $e->getRequest()->getMethod();
            }
        ),
        'failconnecttimeout' => $client->getAsync('https://httpbin.org/delay/2')->then(
            function (ResponseInterface $res) {
                echo $res->getStatusCode() . "\n";
            },
            function (RequestException $e) {
                echo $e->getMessage() . "\n";
                echo $e->getRequest()->getMethod();
            }
        ),
        'fail500' => $client->getAsync('https://httpbin.org/status/500')->then(
            function (ResponseInterface $res) {
                echo $res->getStatusCode() . "\n";
            },
            function (RequestException $e) {
                echo $e->getMessage() . "\n";
                echo $e->getRequest()->getMethod();
            }
        ),
      ];

  $results = Promise\settle($promises)->wait();
person user969068    schedule 13.11.2018
comment
Вау, это здорово. Я сделаю это ответом, но прежде чем я это сделаю... пока я ждал ответов, я просто сделал это... я обернул Promise\unwrap try {}, а затем перехватил исключения для GuzzleHttp\Exception\ConnectException... есть ли недостатки в этом? путь против вашего пути (который намного сложнее)? - person Tallboy; 13.11.2018
comment
если вы не хотите использовать Pool и в документах четко указано, что он выдает ConnectException, так что абсолютно нормально использовать блок try - person user969068; 13.11.2018
comment
Отлично, спасибо! И является ли «плохим тоном» доступ к таким ответам? $promise['state'] и $promise['value']?... вот так: if ($promise['state'] != 'fulfilled') { continue; } // skip failed requests - person Tallboy; 13.11.2018
comment
почему вы обращаетесь к его свойствам? вы можете просто использовать $response->getStatusCode() или $response->getReasonPhrase(), чтобы определить статус ответа - person user969068; 13.11.2018
comment
Потому что я звоню $results = Promise\settle($promises)->wait();. Как только я перебираю $results, я не могу получить доступ к ->getBody(), потому что ответы завернуты в какой-то массив, например ['state' => 'fulfilled/rejected', 'value' => RespHere] - person Tallboy; 13.11.2018
comment
см. мой обновленный ответ, чтобы узнать о другом способе получить ваш ответ, я постараюсь избегать использования свойств таким образом. - person user969068; 13.11.2018
comment
см. здесь: pastebin.com/A1n0GBna --- Хм, мне не нужно делать обратный вызов для каждого элемента . Мне просто нужно собрать данные в конце из успешных ответов. Вот что у меня есть... кажется, работает отлично, но я боюсь, что это неправильный способ доступа к $promise['state']: pastebin.com/A1n0GBna - person Tallboy; 13.11.2018
comment
хорошо .... также большая проблема с вашим кодом, вы используете установку и распаковку вместе? вы должны использовать только один метод, обновленный pastebin.com/2bczhK6R - person user969068; 13.11.2018
comment
аааааа!!! это совсем не ясно из документов .. СПАСИБО. в этом столько смысла... - person Tallboy; 13.11.2018
comment
Итак, получается, что мне действительно нужно проделать некоторую работу по разбору ответов. Я буду использовать вашу вторую часть вашего ответа, чтобы я мог выполнять эту работу асинхронно (анализируя XML всех ответов). Огромное спасибо!!! - person Tallboy; 13.11.2018
comment
Хорошо, @user969068, вы мне очень помогли. У меня есть только 1 последний вопрос ... нужно ли мне беспокоиться об ошибках асинхронного кода, когда я помещаю код в эти обратные вызовы обещаний (во втором блоке кода в вашем ответе)? Допустим, у меня есть массив $output = []. Могу ли я использовать этот массив внутри всех обратных вызовов обещаний ->then(...), не беспокоясь об ошибках многопоточности? - person Tallboy; 13.11.2018
comment
Выполняет ли он все обещания одновременно? Например, если у меня есть 10 обратных вызовов обещаний, и каждый из них выполняет работу за 1 секунду, будет ли общее время ответа 10 или 1 секундой? - person Tallboy; 13.11.2018