Catching exceptions from Guzzle

155,167

Solution 1

Depending on your project, disabling exceptions for guzzle might be necessary. Sometimes coding rules disallow exceptions for flow control. You can disable exceptions for Guzzle 3 like this:

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

This does not disable curl exceptions for something like timeouts, but now you can get every status code easily:

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

To check, if you got a valid code, you can use something like this:

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

... or better handle all expected codes:

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]);

Solution 2

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.

Solution 3

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.

Solution 4

You need to add a extra parameter with http_errors => false

$request = $client->get($url, ['http_errors' => false]);

Solution 5

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
 }

Share:
155,167
Eric
Author by

Eric

Senior developer with over 15 years experience of web development

Updated on May 21, 2021

Comments

  • Eric
    Eric almost 3 years

    I'm trying to catch exceptions from a set of tests I'm running on an API I'm developing and I'm using Guzzle to consume the API methods. I've got the tests wrapped in a try/catch block but it is still throwing unhandled exception errors. Adding an event listener as described in their docs doesn't seem to do anything. I need to be able to retrieve the responses that have HTTP codes of 500, 401, 400, in fact anything that isn't 200 as the system will set the most appropriate code based on the result of the call if it didn't work.

    Current code example

    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;
    
        }
    

    Even with the specific catch block for the thrown exception type I am still getting back

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

    and all execution on the page stops, as you'd expect. The addition of the BadResponseException catch allowed me to catch 404s correctly, but this doesn't seem to work for 500 or 401 responses. Can anyone suggest where I am going wrong please.

  • Trendfischer
    Trendfischer about 9 years
    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 ==.
  • DanielM
    DanielM about 9 years
    Thanks for mentioning request.options. Resolved my problem and saved me looking it up properly. :)
  • mika
    mika over 8 years
    Or in Guzzle5.3: $client = new \GuzzleHttp\Client(['defaults' => [ 'exceptions' => false ]] );
  • fnagel
    fnagel almost 7 years
    This is no longer possible in Guzzle 6. Any idea how to do this with a middleware?
  • Carson Evans
    Carson Evans about 6 years
    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.
  • Prasad Rajapaksha
    Prasad Rajapaksha almost 6 years
    This was the exact issue I had too. Good answer
  • Dan Barron
    Dan Barron almost 5 years
    This saved my bacon on an urgent project. Thanks Trendfischer and SO!
  • Erlang Parasu
    Erlang Parasu over 4 years
  • Flame
    Flame about 4 years
    sounds like you should be catching GuzzleHttp\Exception\RequestException which is the parent of ConnectException, BadResponseException and TooManyRedirectsException.