Manipule a exceção Guzzle e obtenha o corpo HTTP

122

Gostaria de lidar com erros do Guzzle quando o servidor retornar códigos de status 4xx e 5xx. Eu faço uma solicitação 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->getMessageretorna informações de código, mas não o corpo da resposta HTTP. Como posso obter o corpo da resposta?

domos
fonte
1
Esta questão está relacionada a esta questão stackoverflow.com/questions/17658283/… e as respostas também podem ajudar.
Trendfischer 16/03/16

Respostas:

84

Guzzle 3.x

De acordo com os documentos , você pode capturar o tipo de exceção apropriado ( ClientErrorResponseExceptionpara erros 4xx) e chamar seu getResponse()método para obter o objeto de resposta getBody().

use Guzzle\Http\Exception\ClientErrorResponseException;

...

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

Passar truepara a getBodyfunção indica que você deseja obter o corpo da resposta como uma sequência. Caso contrário, você o receberá como instância da classe Guzzle\Http\EntityBody.

sebbo
fonte
232

Guzzle 6.x

De acordo com os documentos , os tipos de exceção que você precisa capturar são:

  • GuzzleHttp\Exception\ClientException para erros de nível 400
  • GuzzleHttp\Exception\ServerException para erros de 500 níveis
  • GuzzleHttp\Exception\BadResponseException para ambos (é a superclasse deles)

O código para lidar com esses erros agora se parece com isso:

$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
fonte
12
Para mim $response->getBody()->getContents(), retornaria uma string vazia. Eu me deparei com isso nos documentos : \GuzzleHttp\Psr7\str($e->getResponse()) ao transmitir a resposta como uma sequência Psr7, recebi uma mensagem de erro completa e bem formatada.
Andy Place
3
@AndyPlace depois de dar uma olhada no PSR 7 (que não foi referenciado pela seção dos documentos aos quais vinculo no momento em que escrevi esta resposta, mas é agora) não é imediatamente óbvio para mim por que a chamada Psr7\str()teria resultados diferentes para ->getContents(). Você tem um exemplo mínimo demonstrando isso, que pode me deixar entender isso e talvez atualizar esta resposta?
Mark Amery
24
Vale ressaltar que a 'http_errors' => falseopção pode ser aprovada na solicitação Guzzle que desativa o lançamento de exceções. Você pode obter o corpo $response->getBody()independentemente do código de status e testar o código de status, se necessário $response->getStatusCode().
tremby
2
Como o @AndyPlace, $response->getBody()->getContents()me fornece uma string vazia em um caso, não entendo o porquê. Mas using \GuzzleHttp\Psr7\str()retorna toda a resposta HTTP como uma string, e eu faria apenas o corpo HTTP. Como dito na documentação , o corpo pode ser usado convertendo-o em string. $stringBody = (string) $clientException->getResponse()->getBody();
AnthonyB
1
Isso foi feito por mim, embora eu estivesse recebendo um código \GuzzleHttp\Exception\RequestExceptionque retornava um 400status. tente {$ request-> api ('POST', 'endpoint.json'); } catch (RequestException $ e) {print_r ($ e-> getResponse () -> getBody () -> getContents ()); }
jpcaparas
54

Embora as respostas acima sejam boas, elas não capturam erros de rede. Como Mark mencionou, BadResponseException é apenas uma super classe para ClientException e ServerException. Mas RequestException também é uma super classe de BadResponseException. A RequestException será lançada não apenas para erros de 400 e 500, mas também para erros de rede e redirecionamentos infinitos. Então, digamos que você solicite a página abaixo, mas sua rede está funcionando e sua captura está esperando apenas uma BadResponseException. Bem, seu aplicativo lançará um erro.

Nesse caso, é melhor esperar RequestException e procurar uma resposta.

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

}
indivíduo
fonte
é JsonResponseuma aula do Guzzle?
aexl
JsonResponsevem de Symfony
chap
14

A partir de 2019, aqui está o que eu elaborei com as respostas acima e os documentos do Guzzle para lidar com a exceção, obter o corpo da resposta, o código de status, a mensagem e outros itens de resposta às vezes 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. Você obtém as informações da resposta em itens convenientemente separados.

Notas laterais:

Com a catchcláusula, capturamos a classe de exceção raiz PHP da cadeia de herança à \Exceptionmedida que as exceções personalizadas do Guzzle a estendem.

Essa abordagem pode ser útil para casos de uso em que o Guzzle é usado sob o capô, como no Laravel ou no AWS API PHP SDK, para que você não possa capturar a exceção genuína do Guzzle.

Nesse caso, a classe de exceção pode não ser a mencionada nos documentos do Guzzle (por exemplo, GuzzleHttp\Exception\RequestException como exceção raiz do Guzzle).

Portanto, você precisa entender, \Exceptionmas lembre-se de que ainda é a instância da classe de exceção Guzzle.

Embora use com cuidado. Esses invólucros podem tornar $e->getResponse()os métodos genuínos do objeto Guzzle não disponíveis. Nesse caso, você terá que examinar o código fonte da exceção real do invólucro e descobrir como obter status, mensagem etc. em vez de usar $responseos métodos do Guzzle .

Se você telefonar diretamente para o Guzzle, poderá encontrar um GuzzleHttp\Exception\RequestExceptionou qualquer outro mencionado nos documentos de exceção com relação às condições do seu caso de uso.

Valentine Shi
fonte
1
Você não deve chamar métodos em seu $responseobjeto ao lidar com exceções, a menos que tenha verificado $e->hasResponse(), caso contrário, $responsepoderá ocorrer nulle qualquer chamada de método causará um erro fatal.
pwaring
@ guerreiro, é verdade. Exatamente como as exceções do Guzzle dizem. Atualizado a resposta. Obrigado.
Valentine Shi
1
... mas isso ainda é problemático após a correção. Você está capturando todas as exceções, não apenas as do Guzzle, mas depois chamando $e->hasResponseo resultado, um método que, é claro, não existe para exceções que não são do Guzzle. Portanto, se você criar uma exceção não-Guzzle theMethodMayThrowException(), esse código a capturará, tente chamar um método inexistente e trava por causa do método inexistente, ocultando efetivamente a verdadeira causa do erro. Preferível seria pegar em GuzzleHttp\Exception\RequestExceptionvez de Exceptionevitar isso.
Mark Amery
1
@ MarkAmery, seu argumento é perfeitamente válido. Obrigado. Eu atualizei o corpo da resposta.
Valentine Shi
1
@JaberAlNahian fico feliz em ouvir :) essa era minha intenção. Sempre bem-vindo.
Valentine Shi
4

se colocar 'http_errors' => falseem opções de solicitação Guzzle, em seguida, ele iria parar exceção lance enquanto get 4xx ou 5xx erro, como esta: $client->get(url, ['http_errors' => false]). então você analisa a resposta, não importa se está tudo bem ou com erro, ela estaria na resposta para obter mais informações

user8487819
fonte
Esta pergunta é sobre erros de manipulação que não solicitam a interrupção de exceções de erro
Dlk em 8/02