Maneje la excepción Guzzle y obtenga el cuerpo HTTP

122

Me gustaría manejar los errores de Guzzle cuando el servidor devuelve los códigos de estado 4xx y 5xx. Hago una solicitud como esta:

$client = $this->getGuzzleClient();
$request = $client->post($url, $headers, $value);
try {
    $response = $request->send();
    return $response->getBody();
} catch (\Exception $e) {
    // How can I get the response body?
}

$e->getMessagedevuelve información del código pero no el cuerpo de la respuesta HTTP. ¿Cómo puedo obtener el cuerpo de respuesta?

domos
fuente
1
Esta pregunta está relacionada con esta pregunta stackoverflow.com/questions/17658283/… y las respuestas allí también pueden ser de alguna ayuda.
Trendfischer

Respuestas:

84

Guzzle 3.x

Según los documentos , puede capturar el tipo de excepción apropiado ( ClientErrorResponseExceptionpara errores 4xx) y llamar a su getResponse()método para obtener el objeto de respuesta, luego invocar getBody()eso:

use Guzzle\Http\Exception\ClientErrorResponseException;

...

try {
    $response = $request->send();
} catch (ClientErrorResponseException $exception) {
    $responseBody = $exception->getResponse()->getBody(true);
}

Pasar truea la getBodyfunción indica que desea obtener el cuerpo de respuesta como una cadena. De lo contrario, lo obtendrá como instancia de clase Guzzle\Http\EntityBody.

sebbo
fuente
232

Guzzle 6.x

Según los documentos , los tipos de excepción que puede necesitar atrapar son:

  • GuzzleHttp\Exception\ClientException para errores de 400 niveles
  • GuzzleHttp\Exception\ServerException para errores de 500 niveles
  • GuzzleHttp\Exception\BadResponseException para ambos (es su superclase)

El código para manejar tales errores ahora se ve así:

$client = new GuzzleHttp\Client;
try {
    $client->get('http://google.com/nosuchpage');    
}
catch (GuzzleHttp\Exception\ClientException $e) {
    $response = $e->getResponse();
    $responseBodyAsString = $response->getBody()->getContents();
}
Mark Amery
fuente
12
Para mí $response->getBody()->getContents()devolvería una cadena vacía. Luego me topé con esto en los documentos : \GuzzleHttp\Psr7\str($e->getResponse()) al enviar la respuesta como una cadena Psr7, recibí un mensaje de error bien formateado y completo.
Andy Place
3
@AndyPlace después de echar un vistazo a PSR 7 (que no se mencionó en la sección de los documentos a los que enlazo en el momento en que escribí esta respuesta, pero es ahora) no es inmediatamente obvio para mí por qué las llamadas Psr7\str()tendrían resultados diferentes a ->getContents(). ¿Tiene un ejemplo mínimo que demuestre esto, que me permita entender esto y quizás actualizar esta respuesta?
Mark Amery
24
Vale la pena mencionar que la 'http_errors' => falseopción se puede pasar en la solicitud Guzzle que deshabilita las excepciones de lanzamiento. Luego puede obtener el cuerpo $response->getBody()sin importar cuál sea el código de estado, y puede probar el código de estado si es necesario con $response->getStatusCode().
temblorosa
2
Como @AndyPlace, $response->getBody()->getContents()me da una cadena vacía en un caso, no entiendo por qué. Pero el uso \GuzzleHttp\Psr7\str()devuelve toda la respuesta HTTP como una cadena, y solo haría el cuerpo HTTP. Como se dice en la documentación , el cuerpo se puede usar al convertirlo en una cadena. $stringBody = (string) $clientException->getResponse()->getBody();
AnthonyB
1
Esto lo hizo por mí, aunque recibí \GuzzleHttp\Exception\RequestExceptionun 400código que me devolvió el estado. pruebe {$ request-> api ('POST', 'endpoint.json'); } catch (RequestException $ e) {print_r ($ e-> getResponse () -> getBody () -> getContents ()); }
jpcaparas
54

Si bien las respuestas anteriores son buenas, no detectarán errores de red. Como Mark mencionó, BadResponseException es solo una superclase para ClientException y ServerException. Pero RequestException también es una superclase de BadResponseException. RequestException se generará no solo por errores de 400 y 500, sino también por errores de red y redirecciones infinitas. Digamos que solicita la página a continuación, pero su red se está reproduciendo y su captura solo espera una BadResponseException. Bueno, tu aplicación arrojará un error.

En este caso, es mejor esperar RequestException y buscar una respuesta.

try {
  $client->get('http://123123123.com')
} catch (RequestException $e) {

  // If there are network errors, we need to ensure the application doesn't crash.
  // if $e->hasResponse is not null we can attempt to get the message
  // Otherwise, we'll just pass a network unavailable message.
  if ($e->hasResponse()) {
    $exception = (string) $e->getResponse()->getBody();
    $exception = json_decode($exception);
    return new JsonResponse($exception, $e->getCode());
  } else {
    return new JsonResponse($e->getMessage(), 503);
  }

}
Cap
fuente
es JsonResponseuna clase de Guzzle?
aexl
JsonResponseviene de Symfony
cap
14

A partir de 2019, esto es lo que elaboré a partir de las respuestas anteriores y los documentos de Guzzle para manejar la excepción, obtener el cuerpo de respuesta, el código de estado, el mensaje y los otros elementos de respuesta a veces valiosos.

try {
    /**
     * We use Guzzle to make an HTTP request somewhere in the
     * following theMethodMayThrowException().
     */
    $result = theMethodMayThrowException();
} catch (\GuzzleHttp\Exception\RequestException $e) {
    /**
     * Here we actually catch the instance of GuzzleHttp\Psr7\Response
     * (find it in ./vendor/guzzlehttp/psr7/src/Response.php) with all
     * its own and its 'Message' trait's methods. See more explanations below.
     *
     * So you can have: HTTP status code, message, headers and body.
     * Just check the exception object has the response before.
     */
    if ($e->hasResponse()) {
        $response = $e->getResponse();
        var_dump($response->getStatusCode()); // HTTP status code;
        var_dump($response->getReasonPhrase()); // Response message;
        var_dump((string) $response->getBody()); // Body, normally it is JSON;
        var_dump(json_decode((string) $response->getBody())); // Body as the decoded JSON;
        var_dump($response->getHeaders()); // Headers array;
        var_dump($response->hasHeader('Content-Type')); // Is the header presented?
        var_dump($response->getHeader('Content-Type')[0]); // Concrete header value;
    }
}
// process $result etc. ...

Voila Obtiene la información de la respuesta en elementos convenientemente separados.

Notas al margen:

Con la catchcláusula, captamos la clase de excepción de raíz PHP de la cadena de herencia a \Exceptionmedida que las excepciones personalizadas de Guzzle la extienden.

Este enfoque puede ser útil para casos de uso en los que Guzzle se usa debajo del capó, como en Laravel o AWS API PHP SDK, por lo que no puede detectar la excepción genuina de Guzzle.

En este caso, la clase de excepción puede no ser la mencionada en los documentos de Guzzle (por ejemplo, GuzzleHttp\Exception\RequestExceptioncomo la excepción raíz para Guzzle).

Por lo tanto, debe atrapar, \Exceptionpero tenga en cuenta que todavía es la instancia de la clase de excepción Guzzle.

Aunque usar con cuidado. Esas envolturas pueden hacer Guzzle$e->getResponse() que los métodos genuinos del objeto no estén disponibles. En este caso, tendrá que mirar el código fuente de excepción real del contenedor y descubrir cómo obtener el estado, el mensaje, etc. en lugar de utilizar $responselos métodos de Guzzle .

Si llama a Guzzle directamente usted mismo, puede atrapar GuzzleHttp\Exception\RequestExceptiono cualquier otro mencionado en sus documentos de excepciones con respecto a las condiciones de su caso de uso.

Valentine Shi
fuente
1
No debe llamar a métodos en su $responseobjeto cuando maneje excepciones a menos que haya marcado $e->hasResponse(), de lo contrario $responsepuede ser nully cualquier llamada a método causará un error fatal.
Pwaring
@pwaring, cierto. Exactamente como dicen las excepciones de Guzzle. Se actualizó la respuesta. Gracias.
Valentine Shi
1
... pero esto sigue siendo problemático después de la solución. Está captando todas las excepciones, no solo las de Guzzle, sino que está llamando $e->hasResponseal resultado, un método que, por supuesto, no existe para las excepciones que no son de Guzzle. Entonces, si genera una excepción que no sea de Guzzle theMethodMayThrowException(), este código lo detectará, intentará llamar a un método inexistente y se bloqueará debido al método inexistente, ocultando efectivamente la verdadera causa del error. Sería preferible atrapar en GuzzleHttp\Exception\RequestExceptionlugar de Exceptionevitar esto.
Mark Amery
1
@ MarkAmery, su punto es perfectamente válido. Gracias. Actualicé el cuerpo de la respuesta.
Valentine Shi
1
@JaberAlNahian contento de escuchar :) esa fue mi intención. Siempre bienvenido.
Valentine Shi
4

Si se pone 'http_errors' => falseen las opciones de solicitud Guzzle, entonces sería dejar de excepción tiro mientras get 4xx o 5xx de error, así: $client->get(url, ['http_errors' => false]). luego analiza la respuesta, no importa si está bien o es un error, estaría en la respuesta para obtener más información

usuario8487819
fuente
Esta pregunta trata sobre los errores de manejo que no requieren excepciones de errores de detención
Dlk