Ловля исключений из Guzzle

80

Я пытаюсь перехватить исключения из набора тестов, которые я запускаю в разрабатываемом мной API, и использую Guzzle для использования методов API. У меня есть тесты, заключенные в блок try / catch, но он по-прежнему выдает необработанные ошибки исключения. Добавление прослушивателя событий, как описано в их документации, похоже, ничего не дает. Мне нужно получить ответы с кодами HTTP 500, 401, 400, фактически все, что не 200, поскольку система установит наиболее подходящий код на основе результата вызова, если он не сработал. .

Текущий пример кода

foreach($tests as $test){

        $client = new Client($api_url);
        $client->getEventDispatcher()->addListener('request.error', function(Event $event) {        

            if ($event['response']->getStatusCode() == 401) {
                $newResponse = new Response($event['response']->getStatusCode());
                $event['response'] = $newResponse;
                $event->stopPropagation();
            }            
        });

        try {

            $client->setDefaultOption('query', $query_string);
            $request = $client->get($api_version . $test['method'], array(), isset($test['query'])?$test['query']:array());


          // Do something with Guzzle.
            $response = $request->send();   
            displayTest($request, $response);
        }
        catch (Guzzle\Http\Exception\ClientErrorResponseException $e) {

            $req = $e->getRequest();
            $resp =$e->getResponse();
            displayTest($req,$resp);
        }
        catch (Guzzle\Http\Exception\ServerErrorResponseException $e) {

            $req = $e->getRequest();
            $resp =$e->getResponse();
            displayTest($req,$resp);
        }
        catch (Guzzle\Http\Exception\BadResponseException $e) {

            $req = $e->getRequest();
            $resp =$e->getResponse();
            displayTest($req,$resp);
        }
        catch( Exception $e){
            echo "AGH!";
        }

        unset($client);
        $client=null;

    }

Даже с конкретным блоком catch для выбранного типа исключения я все равно возвращаюсь

Fatal error: Uncaught exception 'Guzzle\Http\Exception\ClientErrorResponseException' with message 'Client error response [status code] 401 [reason phrase] Unauthorized [url]

и все выполнение на странице останавливается, как и следовало ожидать. Добавление ловушки BadResponseException позволило мне правильно поймать 404, но, похоже, это не работает для 500 или 401 ответа. Может ли кто-нибудь подсказать, где я ошибаюсь, пожалуйста.

Эрик
источник
3
Этот код находится в пространстве имен? Если это так, то, если вы не вводите useисключения, вам может потребоваться префикс ``, чтобы явно указать класс FQ. Так, например, '\ Guzzle \ Http \ Exception \ ClientErrorResponseException'
Энтони Стерлинг,

Ответы:

17

Если в этом tryблоке выбрасывается исключение, то в худшем случае Exceptionследует перехватить что-нибудь неперехваченное.

Учтите, что первая часть теста выбрасывает исключение и также переносит его в tryблок.


источник
1
Вы правы, вне try / catch был тест, который генерировал исключение. Глупая ошибка, спасибо за помощь.
Эрик
146

В зависимости от вашего проекта может потребоваться отключение исключений для жрать. Иногда правила кодирования запрещают исключения для управления потоком. Вы можете отключить исключения для Guzzle 3 следующим образом:

$client = new \Guzzle\Http\Client($httpBase, array(
  'request.options' => array(
     'exceptions' => false,
   )
));

Это не отключает исключения curl для чего-то вроде тайм-аутов, но теперь вы можете легко получить любой код состояния:

$request = $client->get($uri);
$response = $request->send();
$statuscode = $response->getStatusCode();

Чтобы проверить, есть ли у вас действительный код, вы можете использовать что-то вроде этого:

if ($statuscode > 300) {
  // Do some error handling
}

... или лучше обработать все ожидаемые коды:

if (200 === $statuscode) {
  // Do something
}
elseif (304 === $statuscode) {
  // Nothing to do
}
elseif (404 === $statuscode) {
  // Clean up DB or something like this
}
else {
  throw new MyException("Invalid response from api...");
}

For Guzzle 5.3

$client = new \GuzzleHttp\Client(['defaults' => [ 'exceptions' => false ]] );

Thanks to @mika

For Guzzle 6

$client = new \GuzzleHttp\Client(['http_errors' => false]);
Trendfischer
источник
10
Ever had a strange bug caused by a missing break ;-) But sure, it would be a good solution if you have multiple status codes you have to handle in the same way. I prefer if, cause switch just supports ==.
Trendfischer
Thanks for mentioning request.options. Resolved my problem and saved me looking it up properly. :)
DanielM
2
Or in Guzzle5.3: $client = new \GuzzleHttp\Client(['defaults' => [ 'exceptions' => false ]] );
mika
This saved my bacon on an urgent project. Thanks Trendfischer and SO!
Dan Barron
46

To catch Guzzle errors you can do something like this:

try {
    $response = $client->get('/not_found.xml')->send();
} catch (Guzzle\Http\Exception\BadResponseException $e) {
    echo 'Uh oh! ' . $e->getMessage();
}

... but, to be able to "log" or "resend" your request try something like this:

// Add custom error handling to any request created by this client
$client->getEventDispatcher()->addListener(
    'request.error', 
    function(Event $event) {

        //write log here ...

        if ($event['response']->getStatusCode() == 401) {

            // create new token and resend your request...
            $newRequest = $event['request']->clone();
            $newRequest->setHeader('X-Auth-Header', MyApplication::getNewAuthToken());
            $newResponse = $newRequest->send();

            // Set the response object of the request without firing more events
            $event['response'] = $newResponse;

            // You can also change the response and fire the normal chain of
            // events by calling $event['request']->setResponse($newResponse);

            // Stop other events from firing when you override 401 responses
            $event->stopPropagation();
        }

});

... or if you want to "stop event propagation" you can overridde event listener (with a higher priority than -255) and simply stop event propagation.

$client->getEventDispatcher()->addListener('request.error', function(Event $event) {
if ($event['response']->getStatusCode() != 200) {
        // Stop other events from firing when you get stytus-code != 200
        $event->stopPropagation();
    }
});

thats a good idea to prevent guzzle errors like:

request.CRITICAL: Uncaught PHP Exception Guzzle\Http\Exception\ClientErrorResponseException: "Client error response

in your application.

Dado
источник
6
This is no longer possible in Guzzle 6. Any idea how to do this with a middleware?
fnagel
30

In my case I was throwing Exception on a namespaced file, so php tried to catch My\Namespace\Exception therefore not catching any exceptions at all.

Worth checking if catch (Exception $e) is finding the right Exception class.

Just try catch (\Exception $e) (with that \ there) and see if it works.

dmmd
источник
4
I wish I had scrolled down to this error the first time I had the same question. For me I was using outdated Guzzle Exception names and not catching the generic Exception because I wasn't at the root Namesapce. Adding the backslash before Exception started catching the generic Exception allowing me to see my name mismatch errors on the more specific Guzzle Exceptions. See comments on stackoverflow.com/a/7892917/2829359.
Carson Evans
This was the exact issue I had too. Good answer
Prasad Rajapaksha
5

Old question, but Guzzle adds the response within the exception object. So a simple try-catch on GuzzleHttp\Exception\ClientException and then using getResponse on that exception to see what 400-level error and continuing from there.

Carson Reinke
источник
2

I was catching GuzzleHttp\Exception\BadResponseException as @dado is suggesting. But one day I got GuzzleHttp\Exception\ConnectException when DNS for domain wasn't available. So my suggestion is - catch GuzzleHttp\Exception\ConnectException to be safe about DNS errors as well.

Ivan Yarych
источник
sounds like you should be catching GuzzleHttp\Exception\RequestException which is the parent of ConnectException, BadResponseException and TooManyRedirectsException.
Flame
1

I want to update the answer for exception handling in Psr-7 Guzzle, Guzzle7 and HTTPClient(expressive, minimal API around the Guzzle HTTP client provided by laravel).

Guzzle7 (same works for Guzzle 6 as well)

Using RequestException, RequestException catches any exception that can be thrown while transferring requests.

try{
  $client = new \GuzzleHttp\Client(['headers' => ['Authorization' => 'Bearer ' . $token]]);
  
  $guzzleResponse = $client->get('/foobar');
  // or can use
  // $guzzleResponse = $client->request('GET', '/foobar')
    if ($guzzleResponse->getStatusCode() == 200) {
         $response = json_decode($guzzleResponse->getBody(),true);
         //perform your action with $response 
    } 
}
catch(\GuzzleHttp\Exception\RequestException $e){
   // you can catch here 400 response errors and 500 response errors
   // You can either use logs here use Illuminate\Support\Facades\Log;
   $error['error'] = $e->getMessage();
   $error['request'] = $e->getRequest();
   if($e->hasResponse()){
       if ($e->getResponse()->getStatusCode() == '400'){
           $error['response'] = $e->getResponse(); 
       }
   }
   Log::error('Error occurred in get request.', ['error' => $error]);
}catch(Exception $e){
   //other errors 
}

Psr7 Guzzle

use GuzzleHttp\Psr7;
use GuzzleHttp\Exception\RequestException;

try {
    $client->request('GET', '/foo');
} catch (RequestException $e) {
    $error['error'] = $e->getMessage();
    $error['request'] = Psr7\Message::toString($e->getRequest());
    if ($e->hasResponse()) {
        $error['response'] = Psr7\Message::toString($e->getResponse());
    }
    Log::error('Error occurred in get request.', ['error' => $error]);
}

For HTTPClient

use Illuminate\Support\Facades\Http;
try{
    $response = Http::get('http://api.foo.com');
    if($response->successful()){
        $reply = $response->json();
    }
    if($response->failed()){
        if($response->clientError()){
            //catch all 400 exceptions
            Log::debug('client Error occurred in get request.');
            $response->throw();
        }
        if($response->serverError()){
            //catch all 500 exceptions
            Log::debug('server Error occurred in get request.');
            $response->throw();
        }
        
    }
 }catch(Exception $e){
     //catch the exception here
 }

bhucho
источник