Atrapando excepciones de Guzzle


80

Estoy tratando de detectar excepciones de un conjunto de pruebas que estoy ejecutando en una API que estoy desarrollando y estoy usando Guzzle para consumir los métodos de la API. Tengo las pruebas envueltas en un bloque try / catch pero todavía arroja errores de excepción no manejados. Agregar un detector de eventos como se describe en sus documentos no parece hacer nada. Necesito poder recuperar las respuestas que tienen códigos HTTP de 500, 401, 400, de hecho, cualquier cosa que no sea 200, ya que el sistema establecerá el código más apropiado según el resultado de la llamada si no funcionó .

Ejemplo de código actual

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;

    }

Incluso con el bloque de captura específico para el tipo de excepción lanzada, todavía estoy recuperando

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

y toda la ejecución en la página se detiene, como era de esperar. La adición de la captura BadResponseException me permitió detectar 404 correctamente, pero esto no parece funcionar para respuestas 500 o 401. ¿Alguien puede sugerir dónde me estoy equivocando, por favor?


3
¿Este código está en un espacio de nombres? Si es así, a menos que esté useusando las excepciones, es posible que deba anteponerlas con `` para indicar explícitamente la clase FQ. Entonces, por ejemplo, '\ Guzzle \ Http \ Exception \ ClientErrorResponseException'
Anthony Sterling

Respuestas:


17

Si la excepción se lanza en ese trybloque, en el peor de los casos, Exceptiondebería detectar cualquier cosa que no se haya detectado.

Considere que la primera parte de la prueba es lanzar la excepción y envolver eso en el trybloque también.


1
Tiene razón, hubo una prueba fuera del intento / captura que arrojaba la excepción. Error tonto, gracias por la ayuda.
Eric

146

Dependiendo de su proyecto, puede ser necesario deshabilitar las excepciones para tragar. A veces, las reglas de codificación no permiten excepciones para el control de flujo. Puede deshabilitar las excepciones para Guzzle 3 de esta manera:

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

Esto no deshabilita las excepciones de curl para algo como tiempos de espera, pero ahora puede obtener todos los códigos de estado fácilmente:

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

Para verificar, si tiene un código válido, puede usar algo como esto:

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

... o mejor maneja todos los códigos esperados:

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

Para Guzzle 5.3

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

Gracias a @mika

Para Guzzle 6

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

10
Alguna vez ha tenido un error extraño causado por una falta break;-) Pero claro, sería una buena solución si tiene varios códigos de estado que tiene que manejar de la misma manera. Prefiero if, porque el interruptor solo admite ==.
Trendfischer

Gracias por mencionar request.options. Resolvió mi problema y me salvó de buscarlo correctamente. :)
DanielM

2
O en Guzzle5.3: $ client = new \ GuzzleHttp \ Client (['defaults' => ['exceptions' => false]]);
mika

Esto salvó mi tocino en un proyecto urgente. ¡Gracias Trendfischer y SO!
Dan Barron

46

Para detectar errores de Guzzle, puede hacer algo como esto:

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

... pero, para poder "registrar" o "reenviar" su solicitud, intente algo como esto:

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

});

... o si desea "detener la propagación de eventos", puede anular el detector de eventos (con una prioridad superior a -255) y simplemente detener la propagación de eventos.

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

esa es una buena idea para evitar errores de trago como:

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

en su aplicación.


6
Esto ya no es posible en Guzzle 6. ¿Alguna idea de cómo hacer esto con un middleware?
fnagel

30

En mi caso, estaba lanzando Exceptionun archivo con espacio de nombres, por lo que php trató de detectar, My\Namespace\Exceptionpor lo tanto, no detectó ninguna excepción.

Vale la pena comprobar si catch (Exception $e)está encontrando la Exceptionclase adecuada .

Intente catch (\Exception $e)(con eso \allí) y vea si funciona.


4
Ojalá me hubiera desplazado hasta este error la primera vez que tuve la misma pregunta. Para mí, estaba usando nombres de excepción Guzzle obsoletos y no detectaba la excepción genérica porque no estaba en la raíz Namesapce. Agregar la barra invertida antes de que Exception comenzara a detectar la Exception genérica, lo que me permite ver los errores de discrepancia de mi nombre en las Excepciones Guzzle más específicas. Vea los comentarios en stackoverflow.com/a/7892917/2829359 .
Carson Evans

Este fue el problema exacto que tuve también. Buena respuesta
Prasad Rajapaksha


5

Pregunta anterior, pero Guzzle agrega la respuesta dentro del objeto de excepción. Entonces, un simple intento GuzzleHttp\Exception\ClientExceptiony luego usar getResponseesa excepción para ver qué error de nivel 400 y continuar desde allí.


2

Estaba atrapando GuzzleHttp\Exception\BadResponseExceptioncomo sugiere @dado. Pero un día me llegó GuzzleHttp\Exception\ConnectExceptioncuando el DNS para el dominio no estaba disponible. Así que mi sugerencia es: también, GuzzleHttp\Exception\ConnectExceptiontenga cuidado con los errores de DNS.


parece que debería estar detectando GuzzleHttp\Exception\RequestExceptioncuál es el padre de ConnectException, BadResponseExceptiony TooManyRedirectsException.
Flame

1

Quiero actualizar la respuesta para el manejo de excepciones en Psr-7 Guzzle, Guzzle7 y HTTPClient (API expresiva y mínima alrededor del cliente HTTP Guzzle proporcionado por laravel).

Guzzle7 (lo mismo funciona para Guzzle 6 también)

Con RequestException , RequestException detecta cualquier excepción que se pueda lanzar mientras se transfieren solicitudes.

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

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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.