Catch exception from guzzle - php

I'm using laravel and I have setup the abstract class method to get response from the various APIs I'm calling. But if the API url is unreachable then it throws an exception. I know I'm missing something. Any help would be great for me.
$offers = [];
try {
$appUrl = parse_url($this->apiUrl);
// Call Api using Guzzle
$client = new Client('' . $appUrl['scheme'] . '://' . $appUrl['host'] . '' . $appUrl['path']);
if ($appUrl['scheme'] == 'https') //If https then disable ssl certificate
$client->setDefaultOption('verify', false);
$request = $client->get('?' . $appUrl['query']);
$response = $request->send();
if ($response->getStatusCode() == 200) {
$offers = json_decode($response->getBody(), true);
}
} catch (ClientErrorResponseException $e) {
Log::info("Client error :" . $e->getResponse()->getBody(true));
} catch (ServerErrorResponseException $e) {
Log::info("Server error" . $e->getResponse()->getBody(true));
} catch (BadResponseException $e) {
Log::info("BadResponse error" . $e->getResponse()->getBody(true));
} catch (\Exception $e) {
Log::info("Err" . $e->getMessage());
}
return $offers;

you should set the guzzehttp client with option 'http_errors' => false,
the example code should be like this, document:guzzlehttp client http-error option explain
Set to false to disable throwing exceptions on an HTTP protocol errors (i.e., 4xx and 5xx responses). Exceptions are thrown by default when HTTP protocol errors are encountered.
$client->request('GET', '/status/500');
// Throws a GuzzleHttp\Exception\ServerException
$res = $client->request('GET', '/status/500', ['http_errors' => false]);
echo $res->getStatusCode();
// 500
$this->client = new Client([
'cookies' => true,
'headers' => $header_params,
'base_uri' => $this->base_url,
'http_errors' => false
]);
$response = $this->client->request('GET', '/');
if ($code = $response->getStatusCode() == 200) {
try {
// do danger dom things in here
}catch (/Exception $e){
//catch
}
}

These exceptions are not defined in guzzle officially.
These Exceptions are defined in AWS SDK for PHP.
For official Guzzle you may just do following.
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ClientException;
....
try {
$response = $this->client->request($method, $url, [
'headers' => $headers,
'form_params' => $form_parameters,
]);
$body = (string)$response->getBody();
} catch (ClientException $e) {
// Do some thing here...
} catch (RequestException $e) {
// Do some thing here...
} catch (\Exception $e) {
// Do some thing here...
}

you could create your own exception
class CustomException extends Exception
{
}
next you throws your own exception from abstract class guzzle exception
catch(ConnectException $ConnectException)
{
throw new CustomException("Api excception ConnectException",1001);
}
and then handdle in in global exception handdler render methos
if($exception instanceof CustomException)
{
dd($exception);
}

Not sure how you declared those exceptions and which Guzzle version are you using but in official documentation those exceptions don't exist.
In your case you are probally missing GuzzleHttp\Exception\ConnectException.

Related

Using Symfony HTTP client with try and catch

I am using Symfony HTTP client in an event subscriber and I set timeout to 0.1. The problem is I want the code to skip the HTTP request if took more than 0.1 second and it timedout and meanwhile no error/exception needs to be thrown. I tried try and catch but it does not work.
public function checkToken()
{
try {
$response = $this->client->request('POST','url/of/api', [
'json' => ['token' => $_ENV['USER_TOKEN']],
'timeout' => 0.1,
]);
}catch (\Exception $e){
}
}
Why it can not be handled via try and catch?
Its because responses are lazy in Symfony.
As this document mentioned:
https://symfony.com/doc/current/http_client.html#handling-exceptions
A solution is to call getContent method in try section.
private $client;
private $user_data;
public function __construct(HttpClientInterface $client)
{
$this->client = $client;
}
public function checkToken(RequestEvent $event)
{
try {
$response = $this->client->request('POST', 'url/of/api', [
'json' => ['token' => $_ENV['USER_TOKEN']],
'timeout' => 0.1,
]);
$this->user_data = $response->getContent();
} catch (\Exception $e) {
echo $e->getMessage();
}
}

Guzzle - Access to code on server error

I connecting to API by Guzzle:
$client = new Client();
try {
$res = $client->post( 'xxx' . $this->url , [
'headers' => $headers,
'json' => $data,
]);
} catch( Exception $e ) {
echo json_decode( $e->getResponse()->getBody(), true );
}
And it's working but when it's 'catch', I need to get code from response but I getting:
Server error: `POST XXXXX` resulted in a `555 Error` response: {"status":"ERROR","errors":[{"message":"Subscribers already exists in this subscribers list","code":1304}]}
And I can't get the code. How to do this?
UPDATE
Here is screen with full response.
Just extract the response from the exception. Guzzle throws a special BadResponseException in case of failure, so take a look at this class.
try {
// ...
} catch (BadResponseException $exception) {
// 555
$exception->getCode();
$appError = json_decode(
$exception->getResponse()->getBody()->getContents(),
true
);
// 1304
$appErrorCode = $appError['errors'][0]['code'];
}

Why I get Fatal error: Uncaught exception 'GuzzleHttp\Exception\ClientException' with message 'Client error: 404'?

I try catch exception, but i still get "Fatal error: Uncaught exception 'GuzzleHttp\Exception\ClientException' with message 'Client error: 404' in C:\OS\OpenServer\domains\kinopoisk\parser\php\vendor\guzzlehttp\guzzle\src\Middleware.php:69"
<?php
ini_set('display_errors', 'on');
error_reporting(E_ALL);
set_time_limit(0);
require "vendor/autoload.php";
use GuzzleHttp\Client;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ClientException;
$filmsUrl = [297, 298];
$urlIterator = new ArrayObject($filmsUrl);
$client = new Client([
'base_uri' => 'http://example.com',
'cookies' => true,
]);
foreach ($urlIterator->getIterator() as $key => $value) {
try {
$promise = $client->requestAsync('GET', 'post/' . $value, [
'proxy' => [
'http' => 'tcp://216.190.97.3:3128'
]
]);
$promise->then(
function (ResponseInterface $res) {
echo $res->getStatusCode() . "\n";
},
function (RequestException $e) {
echo $e->getMessage() . "\n";
echo $e->getRequest()->getMethod();
}
);
} catch (ClientException $e) {
echo $e->getMessage() . "\n";
echo $e->getRequest()->getMethod();
}
}
$promise->wait();
What wrong in my code?
I am not sure, but you are catching ClientException only here. Try to catch RequestException, too. Looking at the code in Middleware.php:69 that is the exception class used, but if you want to catch all exceptions, then you need to go for the most abstract exception class, which should be
RuntimeException or GuzzleException.
Try something like this:
try {
// your code here
} catch (RuntimeException $e) {
// catches all kinds of RuntimeExceptions
if ($e instanceof ClientException) {
// catch your ClientExceptions
} else if ($e instanceof RequestException) {
// catch your RequestExceptions
}
}
or you can try following approach
try {
// your code here
} catch (ClientException $e) {
// catches all ClientExceptions
} catch (RequestException $e) {
// catches all RequestExceptions
}
Hope that helps.
<?php
//some code
try {
$promise->wait();
} catch (RequestException $e) {
echo $e->getMessage();
}
In the guzzlehttp requestasync method, the HTTP request is initiated when the wait method is called, not when the requestasync method or the then method is called. Therefore, you need to add try catch to the wait method

How can I make Symfony2 ignore Guzzle Client bad response exception in my custom controller?

function order_confirmationAction($order,$token) {
$client = new \GuzzleHttp\Client();
$answer = $client->post("http://www.fullcommerce.com/rest/public/Qtyresponse",
array('body' => $order)
);
$answer = json_decode($answer);
if ($answer->status=="ACK") {
return $this->render('AcmeDapiBundle:Orders:ack.html.twig', array(
'message' => $answer->message,
));
} else throw new \Symfony\Component\HttpKernel\Exception\HttpException(500, $answer->message);
}
If $client->post() response status code is an "Error 500" Symfony stops the script execution and throw new exception before the json decoding.
How can I force Symfony to ignore $client->post() bad response and execute till the last if statement?
$client = new \GuzzleHttp\Client();
try {
$answer = $client->post("http://www.fullcommerce.com/rest/public/Qtyresponse",
array('body' => $serialized_order)
);
}
catch (\GuzzleHttp\Exception\ServerException $e) {
if ($e->hasResponse()) {
$m = $e->getResponse()->json();
throw new \Symfony\Component\HttpKernel\Exception\HttpException(500, $m['result']['message']);
}
}
I solved like this. In that way I can access to responses of remote server even if it returns an error 500 code.
Per Guzzle documentation:
Guzzle throws exceptions for errors that occur during a transfer.
Specifically, if the API responds with a 500 HTTP error, you shouldn't expect its content to be JSON, and you don't want to parse it, so you're better off re-throwing an exception from there already (or informing the user that something went wrong). I would suggest trying this out:
function order_confirmationAction($order, $token) {
$client = new \GuzzleHttp\Client();
try {
$answer = $client->post("http://www.fullcommerce.com/rest/public/Qtyresponse",
array('body' => $order)
);
}
catch (Exception $e) {
throw new \Symfony\Component\HttpKernel\Exception\HttpException(500, $e->getMessage());
}
$answer = json_decode($answer);
if ($answer->status=="ACK") {
return $this->render('AcmeDapiBundle:Orders:ack.html.twig', array(
'message' => $answer->message,
));
} else {
throw new \Symfony\Component\HttpKernel\Exception\HttpException(500, $answer->message);
}
}
It is probably also a good idea to check for errors when JSON-decoding the response, because there could be surprises in the content you're getting (eg. wrong format, missing or unexpected fields or values, etc.).

Catching exceptions from Guzzle

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.
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]);
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.
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.
If the Exception is being thrown in that try block then at worst case scenario Exception should be catching anything uncaught.
Consider that the first part of the test is throwing the Exception and wrap that in the try block as well.
You need to add a extra parameter with http_errors => false
$request = $client->get($url, ['http_errors' => false]);
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
}
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.
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.
If you are using the latest version say 6^ and you have a JSON parameter, you can add 'http_errors' => false to the array together with the JSON as seen below
I was looking out for away to do this i.e with my JSON in there but couldn't find a straight answer.

Categories