Łapanie wyjątków od Guzzle

80

Próbuję wyłapać wyjątki z zestawu testów, które uruchamiam w API, które tworzę i używam Guzzle do korzystania z metod API. Mam testy opakowane w blok try / catch, ale nadal generują nieobsłużone błędy wyjątków. Dodanie detektora zdarzeń zgodnie z opisem w ich dokumentach wydaje się nic nie robić. Muszę być w stanie pobrać odpowiedzi, które mają kody HTTP 500, 401, 400, w rzeczywistości wszystko, co nie jest 200, ponieważ system ustawi najbardziej odpowiedni kod na podstawie wyniku połączenia, jeśli to nie zadziała .

Aktualny przykład kodu

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;

    }

Nawet z konkretnym blokiem catch dla typu rzuconego wyjątku wciąż wracam

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

i jak można się spodziewać, wszystkie operacje na stronie zostają zatrzymane. Dodanie złapania BadResponseException pozwoliło mi poprawnie wyłapać 404, ale wydaje się, że nie działa to dla odpowiedzi 500 lub 401. Czy ktoś może zasugerować, gdzie idę źle?

Eric
źródło
3
Czy ten kod znajduje się w przestrzeni nazw? Jeśli tak, chyba że tworzysz usewyjątki, być może będziesz musiał poprzedzić je znakiem ``, aby jawnie określić klasę FQ. Na przykład „\ Guzzle \ Http \ Exception \ ClientErrorResponseException”
Anthony Sterling

Odpowiedzi:

17

Jeśli wyjątek jest rzucany w tym trybloku, w najgorszym przypadku Exceptionpowinien złapać coś, co nie zostało złapane.

Weź pod uwagę, że pierwsza część testu polega na wyrzuceniu wyjątku i zawinięcie go również w trybloku.


źródło
1
Masz rację, był test poza try / catch, który zgłosił wyjątek. Głupi błąd, dzięki za pomoc.
Eric,
146

W zależności od projektu może być konieczne wyłączenie wyjątków dla guzzle. Czasami reguły kodowania nie zezwalają na wyjątki do sterowania przepływem. Możesz wyłączyć wyjątki dla Guzzle 3 w następujący sposób:

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

Nie wyłącza to wyjątków curl dla czegoś takiego jak limity czasu, ale teraz możesz łatwo uzyskać każdy kod stanu:

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

Aby sprawdzić, czy masz prawidłowy kod, możesz użyć czegoś takiego:

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

... lub lepiej obsłuż wszystkie oczekiwane kody:

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

Dla Guzzle 5.3

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

Dzięki @mika

Dla Guzzle 6

$client = new \GuzzleHttp\Client(['http_errors' => false]);
Trendfischer
źródło
10
Miałeś kiedyś dziwny błąd spowodowany brakiem break;-) Ale na pewno byłoby dobrym rozwiązaniem, gdybyś miał wiele kodów statusu, które musisz obsługiwać w ten sam sposób. Wolę if, bo przełącznik tylko obsługuje ==.
Trendfischer
Dzięki za wzmiankę request.options. Rozwiązałem mój problem i zaoszczędziłem na poprawnym wyszukiwaniu. :)
DanielM
2
Lub w Guzzle5.3: $ client = new \ GuzzleHttp \ Client (['defaults' => ['exceptions' => false]]);
mika
To uratowało mój bekon w pilnym projekcie. Dzięki Trendfischer i SO!
Dan Barron,
46

Aby złapać błędy Guzzle, możesz zrobić coś takiego:

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

... ale aby móc "zarejestrować" lub "ponownie wysłać" swoje żądanie, spróbuj czegoś takiego:

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

});

... lub jeśli chcesz „zatrzymać propagację zdarzeń”, możesz przesłonić detektor zdarzeń (z wyższym priorytetem niż -255) i po prostu zatrzymać propagację zdarzeń.

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

to dobry pomysł, aby uniknąć błędów związanych z żartem, takich jak:

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

we wniosku.

Boazeria
źródło
6
Nie jest to już możliwe w Guzzle 6. Masz pomysł, jak to zrobić za pomocą oprogramowania pośredniego?
fnagel
30

W moim przypadku Exceptionwrzuciłem plik z przestrzenią nazw, więc php próbował złapać, My\Namespace\Exceptionwięc nie wychwytuje żadnych wyjątków.

Warto sprawdzić, czy catch (Exception $e)szuka odpowiedniej Exceptionklasy.

Po prostu spróbuj catch (\Exception $e)(z tym \tam) i zobacz, czy to działa.

dmmd
źródło
4
Żałuję, że nie przewinięto w dół do tego błędu, gdy po raz pierwszy miałem to samo pytanie. U mnie używałem przestarzałych nazw wyjątków Guzzle i nie łapałem ogólnego wyjątku, ponieważ nie byłem w katalogu głównym Namesapce. Dodanie ukośnika odwrotnego, zanim wyjątek zaczął przechwytywać ogólny wyjątek, umożliwiając mi zobaczenie błędów niezgodności moich nazw w bardziej szczegółowych wyjątkach Guzzle. Zobacz komentarze na stackoverflow.com/a/7892917/2829359 .
Carson Evans,
To był dokładnie ten problem, który miałem. Dobra odpowiedź
Prasad Rajapaksha
5

Stare pytanie, ale Guzzle dodaje odpowiedź w obiekcie wyjątku. A więc po prostu spróbuj złapać, GuzzleHttp\Exception\ClientExceptiona następnie użyć getResponsetego wyjątku, aby zobaczyć, jaki błąd 400 poziomu i kontynuować od tego momentu.

Carson Reinke
źródło
2

I był zaraźliwy GuzzleHttp\Exception\BadResponseExceptionjak @dado sugeruje. Ale pewnego dnia dostałem, GuzzleHttp\Exception\ConnectExceptionkiedy DNS dla domeny nie był dostępny. Tak więc moja sugestia jest taka - złap GuzzleHttp\Exception\ConnectExceptionsię również na błędach DNS.

Ivan Yarych
źródło
brzmi jak powinno być zaraźliwy GuzzleHttp\Exception\RequestExceptionktóry jest rodzicem ConnectException, BadResponseExceptiona TooManyRedirectsException.
Płomień
1

Chcę zaktualizować odpowiedź dotyczącą obsługi wyjątków w Psr-7 Guzzle, Guzzle7 i HTTPClient (ekspresyjne, minimalne API wokół klienta HTTP Guzzle dostarczonego przez laravel).

Guzzle7 (to samo działa również dla Guzzle 6)

Korzystając z RequestException , RequestException przechwytuje każdy wyjątek, który może zostać zgłoszony podczas przesyłania żądań.

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

W przypadku 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
źródło