Обработка исключения Guzzle и получение тела HTTP

122

Я хотел бы обрабатывать ошибки от Guzzle, когда сервер возвращает коды состояния 4xx и 5xx. Я делаю такую ​​просьбу:

$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->getMessageвозвращает информацию о коде, но не тело HTTP-ответа. Как получить тело ответа?

ДОМОС
источник
1
Этот вопрос связан с этим вопросом stackoverflow.com/questions/17658283/…, и ответы на него тоже могут помочь.
Trendfischer

Ответы:

84

Жрать 3.x

в документации , вы можете поймать соответствующий тип исключения ( ClientErrorResponseExceptionдля ошибок 4xx) и вызвать его getResponse()метод, чтобы получить объект ответа, а затем вызвать его getBody():

use Guzzle\Http\Exception\ClientErrorResponseException;

...

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

Переходя trueкgetBody функции означает, что вы хотите получить тело ответа в виде строки. В противном случае вы получите его как экземпляр класса Guzzle\Http\EntityBody.

sebbo
источник
232

Жрать 6.x

Согласно документации , вам могут потребоваться следующие типы исключений:

  • GuzzleHttp\Exception\ClientException для 400-уровневых ошибок
  • GuzzleHttp\Exception\ServerException для 500-уровневых ошибок
  • GuzzleHttp\Exception\BadResponseException для обоих (это их суперкласс)

Код для обработки таких ошибок теперь выглядит примерно так:

$client = new GuzzleHttp\Client;
try {
    $client->get('http://google.com/nosuchpage');    
}
catch (GuzzleHttp\Exception\ClientException $e) {
    $response = $e->getResponse();
    $responseBodyAsString = $response->getBody()->getContents();
}
Марк Эмери
источник
12
Для меня $response->getBody()->getContents()вернет пустую строку. Затем я наткнулся на это в документации : \GuzzleHttp\Psr7\str($e->getResponse()) преобразование ответа в виде строки Psr7 дало мне хорошо отформатированное и полное сообщение об ошибке.
Andy Place
3
@AndyPlace после взгляда на PSR 7 (который не упоминался в разделе документов, на который я ссылаюсь в то время, когда я писал этот ответ, но сейчас), мне не сразу очевидно, почему вызов Psr7\str()будет иметь разные результаты к ->getContents(). У вас есть минимальный пример, демонстрирующий это, который мог бы позволить мне понять это и, возможно, обновить этот ответ?
Марк Эмери
24
Стоит упомянуть, что 'http_errors' => falseпараметр может быть передан в запросе Guzzle, который отключает выдачу исключений. Затем вы можете получить тело с $response->getBody()любым кодом состояния, и вы можете проверить код состояния, если это необходимо, с помощью $response->getStatusCode().
tremby
2
Поскольку @AndyPlace $response->getBody()->getContents()в одном случае дает мне пустую строку, я не понимаю, почему. Но использование \GuzzleHttp\Psr7\str()возвращает весь ответ HTTP в виде строки, а я бы только тело HTTP. Как сказано в документации , тело можно использовать, преобразовав его в строку. $stringBody = (string) $clientException->getResponse()->getBody();
AnthonyB
1
Это помогло мне, хотя \GuzzleHttp\Exception\RequestExceptionвместо этого я получал сообщение , возвращающее 400код состояния. попробуйте {$ request-> api ('POST', 'endpoint.json'); } catch (RequestException $ e) {print_r ($ e-> getResponse () -> getBody () -> getContents ()); }
jpcaparas
55

Хотя приведенные выше ответы хороши, они не обнаруживают сетевых ошибок. Как уже упоминал Марк, BadResponseException - это просто суперкласс для ClientException и ServerException. Но RequestException также является суперклассом BadResponseException. RequestException будет сгенерирован не только для ошибок 400 и 500, но и для сетевых ошибок и бесконечных перенаправлений. Допустим, вы запрашиваете страницу ниже, но ваша сеть работает, и ваш улов ожидает только исключения BadResponseException. Что ж, ваше приложение выдаст ошибку.

В этом случае лучше ожидать RequestException и проверять наличие ответа.

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

}
глава
источник
это JsonResponseкласс от жрать?
aexl
JsonResponseприходит с Symfony
глава
14

По состоянию на 2019 год, вот что я разработал на основе приведенных выше ответов и документов Guzzle для обработки исключения, получения тела ответа, кода состояния, сообщения и других иногда ценных элементов ответа.

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. ...

Вуаля. Вы получаете информацию об ответе в виде удобно разделенных элементов.

Боковые примечания:

С помощью catchпредложения мы перехватываем класс корневых исключений PHP в цепочке наследования, \Exceptionпоскольку пользовательские исключения Guzzle расширяют его.

Этот подход может быть полезен в тех случаях, когда Guzzle используется под капотом, например, в Laravel или AWS API PHP SDK, поэтому вы не можете поймать настоящее исключение Guzzle.

В этом случае класс исключения может не совпадать с тем, который упоминается в документации Guzzle (например, GuzzleHttp\Exception\RequestExceptionкак корневое исключение для Guzzle).

Поэтому вам нужно поймать \Exceptionвместо этого, но имейте в виду, что это все еще экземпляр класса исключения Guzzle.

Хотя используйте с осторожностью. Эти оболочки могут сделать недоступными $e->getResponse()подлинные методы объекта Guzzle . В этом случае вам нужно будет посмотреть на фактический исходный код исключения оболочки и выяснить, как получить статус, сообщение и т. Д. Вместо использования методов Guzzle $response.

Если вы сами вызываете Guzzle напрямую, вы можете поймать GuzzleHttp\Exception\RequestExceptionили любой другой, упомянутый в их документации по исключениям в отношении условий вашего варианта использования.

Валентин Ши
источник
1
Вы не должны вызывать методы своего $responseобъекта при обработке исключений, если вы не проверили $e->hasResponse(), иначе это $responseможет быть, nullи любой вызов метода вызовет фатальную ошибку.
pwaring 01
@pwaring, правда. Точно так, как говорится в документации по исключениям Guzzle. Обновил ответ. Спасибо.
Валентин Ши
1
... но это все еще проблематично после исправления. Вы перехватываете все исключения, а не только Guzzle, но затем вызываете $e->hasResponseрезультат, метод, который, конечно, не существует для исключений, отличных от Guzzle. Поэтому, если вы вызовете исключение, отличное от Guzzle theMethodMayThrowException(), этот код перехватит его, попытается вызвать несуществующий метод и выйдет из строя из -за несуществующего метода, эффективно скрывая истинную причину ошибки. Лучше было бы поймать, GuzzleHttp\Exception\RequestExceptionа не Exceptionизбегать этого.
Марк Эмери,
1
@MarkAmery, ваша точка зрения совершенно верна. Спасибо. Я обновил тело ответа.
Валентин Ши,
1
@JaberAlNahian рад это слышать :) Это было моим намерением. Всегда пожалуйста.
Валентин Ши
4

если положить 'http_errors' => falseв жрать параметры запроса, то это было бы остановить бросить исключение при прибудет 4xx или 5xx ошибки, как это: $client->get(url, ['http_errors' => false]). затем вы анализируете ответ, независимо от того, в порядке ли он или ошибка, это будет в ответе для получения дополнительной информации

user8487819
источник
Этот вопрос касается ошибок обработки, не
требующих
@Dlk Я думаю, вы неправильно поняли ответ. Такой подход позволяет обрабатывать ответы вручную и, например, самостоятельно проверять код HTTP и соответствующим образом обрабатывать ответы с кодом ошибки HTTP.
Leukipp