Gérer l'exception Guzzle et obtenir le corps HTTP

122

Je voudrais gérer les erreurs de Guzzle lorsque le serveur renvoie les codes d'état 4xx et 5xx. Je fais une demande comme celle-ci:

$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->getMessagerenvoie les informations de code mais pas le corps de la réponse HTTP. Comment puis-je obtenir le corps de la réponse?

domos
la source
1
Cette question est liée à cette question stackoverflow.com/questions/17658283/… et les réponses peuvent également être utiles.
Trendfischer

Réponses:

84

Guzzle 3.x

Selon la documentation , vous pouvez attraper le type d'exception approprié ( ClientErrorResponseExceptionpour les erreurs 4xx) et appeler sa getResponse()méthode pour obtenir l'objet de réponse, puis appeler getBody()cela:

use Guzzle\Http\Exception\ClientErrorResponseException;

...

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

Passer trueà la getBodyfonction indique que vous souhaitez obtenir le corps de la réponse sous forme de chaîne. Sinon, vous l'obtiendrez comme instance de classe Guzzle\Http\EntityBody.

Sébbo
la source
232

Guzzle 6.x

Selon la documentation , les types d'exceptions que vous devrez peut-être attraper sont:

  • GuzzleHttp\Exception\ClientException pour les erreurs de niveau 400
  • GuzzleHttp\Exception\ServerException pour les erreurs de niveau 500
  • GuzzleHttp\Exception\BadResponseException pour les deux (c'est leur superclasse)

Le code pour gérer de telles erreurs ressemble donc maintenant à ceci:

$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
la source
12
Pour moi, $response->getBody()->getContents()je retournerais une chaîne vide. Je suis ensuite tombé sur ceci dans la documentation : \GuzzleHttp\Psr7\str($e->getResponse()) le cast de la réponse en tant que chaîne Psr7 m'a donné un message d'erreur bien formaté et complet.
Andy Place
3
@AndyPlace après avoir jeté un coup d'œil à PSR 7 (qui n'était pas référencé par la section de la documentation à laquelle je lie au moment où j'ai écrit cette réponse, mais qui l'est maintenant), il n'est pas immédiatement évident pour moi pourquoi un appel Psr7\str()aurait des résultats différents à ->getContents(). Avez-vous un exemple minimal démontrant cela, qui pourrait me permettre de comprendre cela et peut-être de mettre à jour cette réponse?
Mark Amery
24
Il convient de mentionner que l' 'http_errors' => falseoption peut être passée dans la requête Guzzle qui désactive le lancement d'exceptions. Vous pouvez ensuite obtenir le corps avec $response->getBody()quel que soit le code d'état, et vous pouvez tester le code d'état si nécessaire avec $response->getStatusCode().
tremby
2
Comme @AndyPlace, $response->getBody()->getContents()me donne une chaîne vide dans un cas, je ne comprends pas pourquoi. Mais en utilisant \GuzzleHttp\Psr7\str()renvoie toute la réponse HTTP sous forme de chaîne, et je ne voudrais que le corps HTTP. Comme indiqué dans la documentation , le corps peut être utilisé en le convertissant en chaîne. $stringBody = (string) $clientException->getResponse()->getBody();
AnthonyB
1
Cela l'a fait pour moi, même si j'obtenais à la \GuzzleHttp\Exception\RequestExceptionplace un 400code de statut. essayez {$ request-> api ('POST', 'endpoint.json'); } catch (RequestException $ e) {print_r ($ e-> getResponse () -> getBody () -> getContents ()); }
jpcaparas
54

Bien que les réponses ci-dessus soient bonnes, elles ne détecteront pas les erreurs de réseau. Comme Mark l'a mentionné, BadResponseException n'est qu'une super classe pour ClientException et ServerException. Mais RequestException est également une super classe de BadResponseException. RequestException sera lancée non seulement pour les erreurs 400 et 500, mais aussi pour les erreurs réseau et les redirections infinies. Supposons que vous demandiez la page ci-dessous mais que votre réseau joue et que votre capture n'attend qu'une BadResponseException. Eh bien, votre application générera une erreur.

Il est préférable dans ce cas d'attendre RequestException et de rechercher une réponse.

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

}
type
la source
est JsonResponseune classe de Guzzle?
aexl
JsonResponsevient de Symfony
chap
14

À partir de 2019, voici ce que j'ai élaboré à partir des réponses ci-dessus et de la documentation Guzzle pour gérer l'exception, obtenir le corps de la réponse, le code d'état, le message et les autres éléments de réponse parfois précieux.

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. Vous obtenez les informations de la réponse dans des éléments séparés de manière pratique.

Notes secondaires:

Avec la catchclause, nous capturons la classe d'exception racine PHP de la chaîne d'héritage \Exceptioncar les exceptions personnalisées Guzzle l'étendent.

Cette approche peut être utile pour les cas d'utilisation où Guzzle est utilisé sous le capot, comme dans Laravel ou AWS API PHP SDK, de sorte que vous ne pouvez pas intercepter la véritable exception Guzzle.

Dans ce cas, la classe d'exception peut ne pas être celle mentionnée dans la documentation Guzzle (par exemple en GuzzleHttp\Exception\RequestExceptiontant qu'exception racine pour Guzzle).

Vous devez donc attraper à la \Exceptionplace, mais gardez à l'esprit qu'il s'agit toujours de l'instance de classe d'exception Guzzle.

Bien utiliser avec précaution. Ces wrappers peuvent rendre $e->getResponse()les méthodes authentiques des objets Guzzle non disponibles. Dans ce cas, vous devrez regarder le code source des exceptions du wrapper et découvrir comment obtenir le statut, le message, etc. au lieu d'utiliser $responseles méthodes de Guzzle .

Si vous appelez Guzzle directement vous-même, vous pouvez attraper GuzzleHttp\Exception\RequestExceptionou tout autre élément mentionné dans leurs documents d'exceptions en ce qui concerne les conditions de votre cas d'utilisation.

Valentine Shi
la source
1
Vous ne devriez pas appeler des méthodes sur votre $responseobjet lors de la manipulation des exceptions , sauf si vous avez vérifié $e->hasResponse(), sinon $responsepeut - être nullet des appels de méthode provoque une erreur fatale.
pwaring
@pwaring, vrai. Exactement comme le disent les exceptions Guzzle. Mise à jour de la réponse. Je vous remercie.
Valentine Shi
1
... mais cela pose toujours problème après le correctif. Vous attrapez toutes les exceptions, pas seulement celles de Guzzle, mais vous appelez ensuite $e->hasResponsele résultat, une méthode qui, bien sûr, n'existe pas pour les exceptions non Guzzle. Donc, si vous générez une exception non-Guzzle à partir de theMethodMayThrowException(), ce code l'attrapera, essaiera d'appeler une méthode inexistante et plantera à cause de la méthode inexistante, masquant ainsi la véritable cause de l'erreur. Il serait préférable d'attraper GuzzleHttp\Exception\RequestExceptionplutôt que d' Exceptionéviter cela.
Mark Amery
1
@MarkAmery, votre argument est parfaitement valable. Je vous remercie. J'ai mis à jour le corps de la réponse.
Valentine Shi le
1
@JaberAlNahian heureux d'entendre :) c'était mon intention. Toujours bienvenu.
Valentine Shi
4

si elles sont mises 'http_errors' => falsedans les options de demande Guzzle, il arrêterait exception throw tout get 4xx ou 5xx erreur, comme ceci: $client->get(url, ['http_errors' => false]). alors vous analysez la réponse, peu importe que ce soit correct ou une erreur, ce serait dans la réponse pour plus d'informations

user8487819
la source
Cette question concerne les erreurs de gestion ne demandant pas l'arrêt des exceptions d'erreur
Dlk