Obsłuż wyjątek Guzzle i pobierz treść HTTP


122

Chciałbym poradzić sobie z błędami Guzzle, gdy serwer zwraca kody stanu 4xx i 5xx. Składam taką prośbę:

$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->getMessagezwraca informacje o kodzie, ale nie treść odpowiedzi HTTP. Jak mogę uzyskać treść odpowiedzi?


1
To pytanie jest związane z tym pytaniem stackoverflow.com/questions/17658283/ ... i odpowiedzi mogą też być pomocne.
Trendfischer

Odpowiedzi:


84

Guzzle 3.x

Zgodnie z dokumentacją możesz złapać odpowiedni typ wyjątku ( ClientErrorResponseExceptiondla błędów 4xx) i wywołać jego getResponse()metodę, aby uzyskać obiekt odpowiedzi, a następnie wywołać getBody()to:

use Guzzle\Http\Exception\ClientErrorResponseException;

...

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

Przekazanie truedo getBodyfunkcji wskazuje, że chcesz pobrać treść odpowiedzi jako ciąg. W przeciwnym razie otrzymasz go jako instancję klasy Guzzle\Http\EntityBody.


232

Guzzle 6.x

Zgodnie z dokumentacją typy wyjątków, które możesz potrzebować, to:

  • GuzzleHttp\Exception\ClientException dla błędów 400-poziomowych
  • GuzzleHttp\Exception\ServerException dla błędów poziomu 500
  • GuzzleHttp\Exception\BadResponseException dla obojga (to ich superklasa)

Kod do obsługi takich błędów wygląda teraz mniej więcej tak:

$client = new GuzzleHttp\Client;
try {
    $client->get('http://google.com/nosuchpage');    
}
catch (GuzzleHttp\Exception\ClientException $e) {
    $response = $e->getResponse();
    $responseBodyAsString = $response->getBody()->getContents();
}

12
Dla mnie $response->getBody()->getContents()zwróciłby pusty ciąg. Następnie natknąłem się na to w dokumentach : \GuzzleHttp\Psr7\str($e->getResponse()) Przesyłanie odpowiedzi jako ciągu Psr7 dało mi ładnie sformatowany i kompletny komunikat o błędzie.
Andy Place

3
@AndyPlace po rzuceniu okiem na PSR 7 (do którego nie odwołano się w sekcji dokumentów, do których odsyłam w momencie, gdy pisałem tę odpowiedź, ale teraz jest) nie jest dla mnie od razu oczywiste, dlaczego wywołanie Psr7\str()miałoby inne wyniki do ->getContents(). Czy masz minimalny przykład demonstrujący to, który może pozwolić mi to zrozumieć i być może zaktualizować tę odpowiedź?
Mark Amery,

24
Warto wspomnieć, że 'http_errors' => falseopcję można przekazać w żądaniu Guzzle, co wyłącza rzucanie wyjątków. Następnie możesz pobrać $response->getBody()treść bez względu na kod stanu, aw razie potrzeby możesz przetestować kod stanu za pomocą $response->getStatusCode().
tremby

2
Ponieważ @AndyPlace $response->getBody()->getContents()daje mi pusty ciąg w jednym przypadku, nie rozumiem dlaczego. Ale użycie \GuzzleHttp\Psr7\str()zwraca całą odpowiedź HTTP jako ciąg, a ja tylko treść HTTP. Jak wspomniano w dokumentacji , ciało może być używane przez odlewanie go na strunę. $stringBody = (string) $clientException->getResponse()->getBody();
AnthonyB

1
Zrobiło to dla mnie, chociaż \GuzzleHttp\Exception\RequestExceptionzamiast tego otrzymywałem, który zwrócił 400kod stanu. try {$ request-> api ('POST', 'endpoint.json'); } catch (RequestException $ e) {print_r ($ e-> getResponse () -> getBody () -> getContents ()); }
jpcaparas

55

Chociaż powyższe odpowiedzi są dobre, nie wyłapią błędów sieciowych. Jak wspomniał Mark, BadResponseException to po prostu super klasa dla ClientException i ServerException. Ale RequestException to także super klasa BadResponseException. RequestException zostanie zgłoszony nie tylko dla błędów 400 i 500, ale także dla błędów sieciowych i nieskończonych przekierowań. Powiedzmy więc, że żądasz poniższej strony, ale Twoja sieć działa, a Twój haczyk oczekuje tylko wyjątku BadResponseException. Twoja aplikacja zgłosi błąd.

W takim przypadku lepiej jest spodziewać się wyjątku RequestException i sprawdzić odpowiedź.

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

}

to JsonResponseklasa z Guzzle?
aexl

JsonResponsepochodzi z Symfony
rozdz.

14

Od 2019 r. Opracowałem na podstawie powyższych odpowiedzi i dokumentów Guzzle, aby obsłużyć wyjątek, uzyskać treść odpowiedzi, kod stanu, wiadomość i inne czasami cenne elementy odpowiedzi.

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. Otrzymujesz informacje o odpowiedzi w wygodnie oddzielonych pozycjach.

Uwagi dodatkowe:

Za pomocą catchklauzuli \Exceptionłapiemy klasę wyjątku głównego PHP łańcucha dziedziczenia, ponieważ niestandardowe wyjątki Guzzle ją rozszerzają.

To podejście może być przydatne w przypadkach użycia, w których Guzzle jest używane pod maską, jak w Laravel lub AWS API PHP SDK, więc nie możesz złapać prawdziwego wyjątku Guzzle.

W tym przypadku klasa wyjątku może nie być tą, o której mowa w dokumentacji Guzzle (np. GuzzleHttp\Exception\RequestExceptionJako główny wyjątek dla Guzzle).

Więc \Exceptionzamiast tego musisz złapać, ale pamiętaj, że nadal jest to instancja klasy wyjątku Guzzle.

Chociaż używaj ostrożnie. Te opakowania mogą sprawić, $e->getResponse()że oryginalne metody obiektu Guzzle będą niedostępne. W takim przypadku będziesz musiał spojrzeć na rzeczywisty kod źródłowy wyjątku opakowania i dowiedzieć się, jak uzyskać status, komunikat itp. Zamiast używać $responsemetod Guzzle .

Jeśli zadzwonisz bezpośrednio do Guzzle, możesz złapać GuzzleHttp\Exception\RequestExceptionlub inny wymieniony w ich dokumentach wyjątków w odniesieniu do warunków twojego przypadku użycia.


1
Nie powinieneś wywoływać metod na swoim $responseobiekcie podczas obsługi wyjątków, chyba że zaznaczyłeś $e->hasResponse(), w przeciwnym razie $responsemoże tak być, nulla każde wywołanie metod spowoduje błąd krytyczny.
pwaring

@pwaring, prawda. Dokładnie tak, jak mówią dokumenty dotyczące wyjątków Guzzle. Zaktualizowałem odpowiedź. Dziękuję Ci.
Valentine Shi

1
... ale po naprawieniu jest to nadal problematyczne. Wyłapujesz wszystkie wyjątki, nie tylko te z Guzzle, ale potem wywołujesz $e->hasResponsewynik, metodę, która oczywiście nie istnieje dla wyjątków innych niż Guzzle. Więc jeśli zgłosisz wyjątek inny niż Guzzle z theMethodMayThrowException(), ten kod złapie go, spróbuje wywołać nieistniejącą metodę i zawiesi się z powodu nieistniejącej metody, skutecznie ukrywając prawdziwą przyczynę błędu. Lepiej byłoby złapać GuzzleHttp\Exception\RequestExceptionzamiast tego Exceptionunikać.
Mark Amery

1
@MarkAmery, twój punkt widzenia jest całkowicie ważny. Dziękuję Ci. Zaktualizowałem treść odpowiedzi.
Valentine Shi

1
@JaberAlNahian miło to słyszeć :) taki był mój zamiar. Zawsze mile widziane.
Valentine Shi

4

jeśli trafi 'http_errors' => falsew chlać opcji żądanie, wtedy byłoby przestać rzucać wyjątek podczas get 4xx lub 5xx błędu w następujący sposób: $client->get(url, ['http_errors' => false]). następnie analizujesz odpowiedź, bez względu na to, czy jest w porządku, czy błąd, będzie w odpowiedzi, aby uzyskać więcej informacji


To pytanie dotyczy błędów obsługi bez pytania o zatrzymanie wyjątków błędów
Dlk

@Dlk Myślę, że źle zrozumiałeś odpowiedź. Takie podejście umożliwia ręczne przetwarzanie odpowiedzi i, na przykład, samodzielne sprawdzanie kodu HTTP i odpowiednią obsługę tych z kodem błędu HTTP.
Leukipp
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.