Handling exceptions on a Pool of Requests

旧街凉风 提交于 2021-01-27 07:03:19

问题


I'm using Guzzle to send a number of requests to an API endpoint, using the Pool functionality to send these asynchronously and concurrently.

The script looks like this:

use GuzzleHttp\Client;
use GuzzleHttp\Promise;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Exception\RequestException;

/* Configure logger */
Logger::configure("config/logger.xml");
$logger = Logger::getLogger("console");

/* Configure Guzzle Client */
$client = new Client([
    'base_uri' => 'http://my.api/',
    'timeout' => 2.0,
    'allow_redirects' => false,
]);

/* Anonymous function (closure) to 'yield' X number of Requests */
$requests = function ($num_requests) {
    for ($i = 0; $i < $num_requests; $i++) {
        yield new Request('GET', "/MY_UNIQUE_IDENTIFIER/");
    }
};

/* Create a Pool for the above requests */
$pool = new Pool($client, $requests(20), [
    'concurrency' => 5,  // Determine how many requests to send concurrently
    'fulfilled' => function ($response, $index) {
        $logger->info('$index: ' . $index . ', $response: ' . $response);
    },
    'rejected' => function ($reason, $index) {
        try {
            echo $reason;
        } catch (\Exception $e) {
            trigger_error($e->getMessage(), E_USER_WARNING);
        }
    },
]);

/* Initiate transfers/create a promise */
$promise = $pool->promise();

/* Force the pool of requests to complete */
$promise->wait();

Basically, send 20 requests (5 at a time) to http://my.api/MY_UNIQUE_IDENTIFIER.

The Pool appears to work. If I add an echo to the rejected requests, I get output like:

#0 /Users/me/guzzle-POC/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php(149): GuzzleHttp\Handler\CurlFactory::createRejection(Object(GuzzleHttp\Handler\EasyHandle), Array)
#1 /Users/me/guzzle-POC/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php(102): GuzzleHttp\Handler\CurlFactory::finishError(Object(GuzzleHttp\Handler\CurlMultiHandler), Object(GuzzleHttp\Handler\EasyHandle), Object(GuzzleHttp\Handler\CurlFactory))
#2 /Users/me/guzzle-POC/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(181): GuzzleHttp\Handler\CurlFactory::finish(Object(GuzzleHttp\Handler\CurlMultiHandler), Object(GuzzleHttp\Handler\EasyHandle), Object(GuzzleHttp\Handler\CurlFactory))
#3 /Users/me/guzzle-POC/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(110): GuzzleHttp\Handler\CurlMultiHandler->processMessages()
#4 /Users/me/guzzle-POC/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(125): GuzzleHttp\Handler\CurlMultiHandler->tick()
#5 /Users/me/guzzle-POC/vendor/guzzlehttp/promises/src/Promise.php(246): GuzzleHttp\Handler\CurlMultiHandler->execute(true)
#6 /Users/me/guzzle-POC/vendor/guzzlehttp/promises/src/Promise.php(223): GuzzleHttp\Promise\Promise->invokeWaitFn()
#7 /Users/me/guzzle-POC/vendor/guzzlehttp/promises/src/Promise.php(267): GuzzleHttp\Promise\Promise->waitIfPending()
#8 /Users/me/guzzle-POC/vendor/guzzlehttp/promises/src/Promise.php(225): GuzzleHttp\Promise\Promise->invokeWaitList()
#9 /Users/me/guzzle-POC/vendor/guzzlehttp/promises/src/Promise.php(62): GuzzleHttp\Promise\Promise->waitIfPending()
#10 /Users/me/guzzle-POC/vendor/guzzlehttp/promises/src/EachPromise.php(101): GuzzleHttp\Promise\Promise->wait()
#11 /Users/me/guzzle-POC/vendor/guzzlehttp/promises/src/Promise.php(246): GuzzleHttp\Promise\EachPromise->GuzzleHttp\Promise\{closure}(true)
#12 /Users/me/guzzle-POC/vendor/guzzlehttp/promises/src/Promise.php(223): GuzzleHttp\Promise\Promise->invokeWaitFn()
#13 /Users/me/guzzle-POC/vendor/guzzlehttp/promises/src/Promise.php(62): GuzzleHttp\Promise\Promise->waitIfPending()
#14 /Users/me/guzzle-POC/poc.php(50): GuzzleHttp\Promise\Promise->wait()
#15 {main}GuzzleHttp\Exception\ConnectException: cURL error 6: Could not resolve host: my.api (see http://curl.haxx.se/libcurl/c/libcurl-errors.html) in /Users/me/guzzle-POC/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:185

The main problem here being #15, Could not resolve host: my.api. This is expected behaviour, but I want to catch this exception.

The try/catch that I've used just doesn't work at all - it catches nothing.

Presumably this is because of the Pool's asynchronous nature, but is it possible to catch these exceptions in any way?

All I want to achieve basically is, if can't resolve; log error and continue with other requests-type approach.


回答1:


I had a similar problem, I would share a solution which worked for me.

Taking example from your question,

This is the picture which gives the tree heirarchy of guzzle exception(source Guzzle Docs.)

Also GuzzleHttp\Exception\ConnectException exception is thrown in the event of a networking error and also ConnectException does not have an associated response, so therefore there is no 400 or 500 error. So it should give false for hasResponse()


$client = new Client([
    'base_uri' => 'http://my.api/',
    'timeout' => 2.0,
    'allow_redirects' => false,
]);

/* Anonymous function (closure) to 'yield' X number of Requests */
$requests = function ($num_requests) {
    for ($i = 0; $i < $num_requests; $i++) {
        yield new Request('GET', "/MY_UNIQUE_IDENTIFIER/");
    }
};

/* Create a Pool for the above requests */
$pool = new Pool($client, $requests(20), [
    'concurrency' => 5,  // Determine how many requests to send concurrently
    'fulfilled' => function ($response, $index) {
        $logger->info('$index: ' . $index . ', $response: ' . $response);
    },
    'rejected' => function (\GuzzleHttp\Exception\TransferException $reason, $index) {
            if ($reason->hasResponse()){
            // this will mainly catch RequestException(Exception with statuscode and responses)
                    if ($reason->getResponse()->getStatusCode() == '400') {
                        // log your exception
                    } elseif($reason->getResponse()->getStatusCode() == '403'){
                        //log your unauthorised code exception 
                    }else{
                        //similarly log your other status code exception 
                    }
            } else{ 
                // ConnectException should come here, you can log it, will not have any responses as the request was never sent.
            }
    },
]);

/* Initiate transfers/create a promise */
$promise = $pool->promise();

/* Force the pool of requests to complete */
$promise->wait();

Demerits

You need to know the exact status code of your exceptions.


Also if someone is specific to only catch ServerException or ClientException, then they can replace TransferException with other exception down the heirarchy tree.

'rejected' => function (\GuzzleHttp\Exception\RequestException $reason, $index) {


来源:https://stackoverflow.com/questions/56183592/handling-exceptions-on-a-pool-of-requests

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!