Ausnahmen von Guzzle abfangen

79

Ich versuche, Ausnahmen von einer Reihe von Tests abzufangen, die ich auf einer von mir entwickelten API ausführe, und verwende Guzzle, um die API-Methoden zu verwenden. Ich habe die Tests in einen Try / Catch-Block eingeschlossen, aber es werden immer noch unbehandelte Ausnahmefehler ausgegeben. Das Hinzufügen eines Ereignis-Listeners, wie in den Dokumenten beschrieben, scheint nichts zu bewirken. Ich muss in der Lage sein, die Antworten mit HTTP-Codes von 500, 401, 400 abzurufen. Tatsächlich muss alles, was nicht 200 ist, da das System den am besten geeigneten Code basierend auf dem Ergebnis des Aufrufs festlegt, wenn es nicht funktioniert hat .

Aktuelles Codebeispiel

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;

    }

Selbst mit dem spezifischen catch-Block für den ausgelösten Ausnahmetyp komme ich immer noch zurück

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

und alle Ausführung auf der Seite stoppt, wie Sie es erwarten würden. Durch das Hinzufügen des BadResponseException-Catch konnte ich 404s korrekt abfangen, dies scheint jedoch bei 500 oder 401 Antworten nicht zu funktionieren. Kann mir jemand vorschlagen, wo ich falsch liege?

Eric
quelle
3
Befindet sich dieser Code unter einem Namespace? Wenn ja, usemüssen Sie ihnen möglicherweise ein Präfix voranstellen, um die FQ-Klasse explizit anzugeben , es sei denn, Sie geben die Ausnahmen an. So zum Beispiel '\ Guzzle \ Http \ Exception \ ClientErrorResponseException'
Anthony Sterling

Antworten:

17

Wenn die Ausnahme in diesem tryBlock ausgelöst wird, ist dies im schlimmsten Fall der FallException alles Ungefangene abfangen.

Bedenken Sie, dass der erste Teil des Tests die Ausnahme auslöst und diese ebenfalls in den tryBlock einschließt.


quelle
1
Sie haben Recht, es gab einen Test außerhalb des Try / Catch, der die Ausnahme auslöste. Dummer Fehler, danke für die Hilfe.
Eric
144

Abhängig von Ihrem Projekt kann es erforderlich sein, Ausnahmen für die Verzehrung zu deaktivieren. Manchmal verbieten Codierungsregeln Ausnahmen für die Flusskontrolle. Sie können Ausnahmen für Guzzle 3 folgendermaßen deaktivieren :

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

Dadurch werden Curl-Ausnahmen für Zeitüberschreitungen nicht deaktiviert, aber jetzt können Sie jeden Statuscode einfach abrufen:

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

Um zu überprüfen, ob Sie einen gültigen Code haben, können Sie Folgendes verwenden:

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

... oder besser mit allen erwarteten Codes umgehen:

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...");
}

Für Guzzle 5.3

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

Danke an @mika

Für Guzzle 6

$client = new \GuzzleHttp\Client(['http_errors' => false]);
Trendfischer
quelle
10
Hatte jemals einen seltsamen Fehler, der durch ein fehlendes verursacht wurde break;-) Aber sicher, es wäre eine gute Lösung, wenn Sie mehrere Statuscodes haben, die Sie auf die gleiche Weise behandeln müssen. Ich bevorzuge if, weil Schalter nur unterstützt ==.
Trendfischer
Vielen Dank für die Erwähnung request.options. Mein Problem wurde behoben und ich konnte es nicht richtig nachschlagen. :)
DanielM
2
Oder in Guzzle5.3: $ client = new \ GuzzleHttp \ Client (['Standard' => ['Ausnahmen' => falsch]]);
Mika
Dies rettete meinen Speck bei einem dringenden Projekt. Danke Trendfischer und SO!
Dan Barron
46

Um Guzzle-Fehler abzufangen, können Sie Folgendes tun:

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

... aber um Ihre Anfrage "protokollieren" oder "erneut senden" zu können, versuchen Sie Folgendes:

// 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();
        }

});

... oder wenn Sie die Ereignisausbreitung stoppen möchten, können Sie den Ereignis-Listener (mit einer höheren Priorität als -255) überschreiben und einfach die Ereignisausbreitung stoppen.

$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();
    }
});

Das ist eine gute Idee, um Fehler wie: zu vermeiden.

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

in Ihrer Bewerbung.

Dado
quelle
6
Dies ist in Guzzle 6 nicht mehr möglich. Haben Sie eine Idee, wie Sie dies mit einer Middleware tun können?
Fnagel
30

In meinem Fall habe ich Exceptionauf eine Namespace-Datei geworfen , also hat PHP versucht zu fangen, My\Namespace\Exceptionalso überhaupt keine Ausnahmen.

Es lohnt sich zu prüfen, ob catch (Exception $e)die richtige ExceptionKlasse gefunden wird.

Versuchen Sie es einfach catch (\Exception $e)(damit \) und sehen Sie, ob es funktioniert.

dmmd
quelle
4
Ich wünschte, ich hätte zu diesem Fehler gescrollt, als ich zum ersten Mal dieselbe Frage hatte. Für mich habe ich veraltete Guzzle Exception-Namen verwendet und die generische Exception nicht abgefangen, weil ich nicht am Stamm-Namesapce war. Hinzufügen des Backslashs, bevor Exception die generische Exception abfängt, sodass ich meine Namensinkongruenzfehler bei den spezifischeren Guzzle Exceptions sehen kann. Siehe Kommentare zu stackoverflow.com/a/7892917/2829359 .
Carson Evans
Dies war genau das Problem, das ich auch hatte. Gute Antwort
Prasad Rajapaksha
5

Alte Frage, aber Guzzle fügt die Antwort innerhalb des Ausnahmeobjekts hinzu. Also ein einfacher Versuch , diese Ausnahme zu erkennen GuzzleHttp\Exception\ClientExceptionund dann zu verwenden getResponse, um zu sehen, welcher 400-Level-Fehler vorliegt, und von dort aus fortzufahren.

Carson Reinke
quelle
2

Ich habe GuzzleHttp\Exception\BadResponseExceptiongefangen , wie @dado vorschlägt. Aber eines Tages bekam ich, GuzzleHttp\Exception\ConnectExceptionals DNS für Domain nicht verfügbar war. Mein Vorschlag ist also - fangen Sie GuzzleHttp\Exception\ConnectExceptionan, auch bei DNS-Fehlern sicher zu sein.

Ivan Yarych
quelle
Klänge mögen , sollten Sie werden zu kontrollieren , GuzzleHttp\Exception\RequestExceptionwelche die Eltern von ConnectException, BadResponseExceptionund TooManyRedirectsException.
Flamme
1

Ich möchte die Antwort für die Ausnahmebehandlung in Psr-7 Guzzle, Guzzle7 und HTTPClient aktualisieren (ausdrucksstarke, minimale API rund um den von laravel bereitgestellten Guzzle-HTTP-Client).

Guzzle7 (das gleiche gilt auch für Guzzle 6)

Mit RequestException fängt RequestException alle Ausnahmen ab, die beim Übertragen von Anforderungen ausgelöst werden können.

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

Für 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
quelle