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

Я хотел бы обрабатывать ошибки от 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-ответа. Как получить тело ответа?


person domos    schedule 02.11.2013    source источник
comment
Этот вопрос связан с этим вопросом stackoverflow.com/questions / 17658283 /, и ответы там тоже могут помочь.   -  person Trendfischer    schedule 16.03.2016


Ответы (9)


Жрать 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.

person sebbo    schedule 02.11.2013

Жрать 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();
}
person Mark Amery    schedule 20.06.2015
comment
Для меня $response->getBody()->getContents() вернет пустую строку. Затем я наткнулся на это в документах: \GuzzleHttp\Psr7\str($e->getResponse()) Трансляция ответ в виде строки Psr7 дал мне хорошо отформатированное и полное сообщение об ошибке. - person Andy Place; 21.12.2016
comment
@AndyPlace после того, как взглянул на PSR 7 (который не упоминался в разделе документов, на который я ссылаюсь в то время, когда я писал этот ответ, но есть сейчас), мне не сразу очевидно, почему вызов Psr7\str() будет иметь другое результаты в ->getContents(). У вас есть минимальный пример, демонстрирующий это, который мог бы позволить мне понять это и, возможно, обновить этот ответ? - person Mark Amery; 21.12.2016
comment
Стоит упомянуть, что параметр 'http_errors' => false может быть передан в запросе Guzzle, который отключает выдачу исключений. Затем вы можете получить тело с помощью $response->getBody() независимо от кода состояния, и вы можете проверить код состояния, если необходимо, с помощью $response->getStatusCode(). - person tremby; 24.08.2017
comment
Как @AndyPlace, $response->getBody()->getContents() дает мне пустую строку в одном случае, я не понимаю почему. Но использование \GuzzleHttp\Psr7\str() возвращает весь ответ HTTP в виде строки, а я бы только тело HTTP. Как сказано в документации, тело можно использовать, преобразовав его в строку. $stringBody = (string) $clientException->getResponse()->getBody(); - person AnthonyB; 12.12.2017
comment
Это помогло мне, хотя вместо этого я получал \GuzzleHttp\Exception\RequestException, который возвращал код состояния 400. попробуйте {$ request- ›api ('POST', 'endpoint.json'); } catch (RequestException $ e) {print_r ($ e- ›getResponse () -› getBody () - ›getContents ()); } - person jpcaparas; 10.04.2019

Хотя приведенные выше ответы хороши, они не обнаруживают сетевых ошибок. Как уже упоминал Марк, 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);
  }

}
person chap    schedule 17.11.2016
comment
JsonResponse это класс от Guzzle? - person aexl; 29.05.2020
comment
JsonResponse исходит от Symfony - person chap; 01.06.2020

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

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.

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

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

person Valentine Shi    schedule 30.06.2019
comment
Вы не должны вызывать методы вашего $response объекта при обработке исключений, если вы не отметили $e->hasResponse(), иначе $response может быть null, и любой вызов метода вызовет фатальную ошибку. - person pwaring; 01.10.2019
comment
@pwaring, правда. Точно так же, как говорится в документации по исключениям Guzzle. Обновил ответ. Спасибо. - person Valentine Shi; 01.10.2019
comment
... но это все еще проблематично после исправления. Вы перехватываете все исключения, а не только Guzzle, но затем вызываете $e->hasResponse для результата, метода, который, конечно, не существует для исключений, отличных от Guzzle. Поэтому, если вы вызовете исключение, отличное от Guzzle, из theMethodMayThrowException(), этот код перехватит его, попытается вызвать несуществующий метод и выйдет из строя из-за несуществующего метода, эффективно скрывая истинную причину ошибки. Во избежание этого предпочтительнее было бы поймать GuzzleHttp\Exception\RequestException вместо Exception. - person Mark Amery; 05.01.2020

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

person ShakalakaB    schedule 08.02.2020
comment
Этот вопрос касается ошибок обработки, не требующих остановки исключений ошибок. - person Dlk; 08.02.2020
comment
@Dlk Я думаю, вы неправильно поняли ответ. Такой подход позволяет обрабатывать ответы вручную и, например, самостоятельно проверять код HTTP и соответствующим образом обрабатывать ответы с кодом ошибки HTTP. - person Leukipp; 13.08.2020

Ни один из вышеперечисленных ответов не работает с ошибкой, у которой нет тела, но есть некоторый описывающий текст. Для меня это была SSL certificate problem: unable to get local issuer certificate ошибка. Поэтому я заглянул прямо в код, потому что документ на самом деле мало что говорит, и сделал следующее (в Guzzle 7.1):

try {
    // call here
} catch (\GuzzleHttp\Exception\RequestException $e) {
    if ($e->hasResponse()) {
        $response = $e->getResponse();
        // message is in $response->getReasonPhrase()
    } else {
        $response = $e->getHandlerContext();
        if (isset($response['error'])) {
            // message is in $response['error']
        } else {
            // Unknown error occured!
        }
    }
}
person Erik    schedule 29.09.2020

Вопрос был такой:

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

Хотя вы можете обрабатывать коды состояния 4xx или 5xx отдельно, на практике имеет смысл перехватывать все исключения и обрабатывать результаты соответствующим образом.

Вопрос также в том, хотите ли вы просто обработать ошибки или получить тело? Я думаю, что в большинстве случаев было бы достаточно обработать ошибки и не получать тело сообщения или получать тело только в случае отсутствия ошибки.

Я бы посмотрел в документации, чтобы проверить, как ваша версия Guzzle справляется с этим, потому что это может измениться: https://docs.guzzlephp.org/en/stable/quickstart.html#exceptions

Также см. Эту страницу в официальной документации по Работа с ошибками, в котором говорится:

Запросы, получившие ответ 4xx или 5xx, будут выдавать Guzzle\Http\Exception\BadResponseException. В частности, ошибки 4xx вызывают Guzzle\Http\Exception\ClientErrorResponseException, а ошибки 5xx вызывают Guzzle\Http\Exception\ServerErrorResponseException. Вы можете перехватить определенные исключения или просто перехватить BadResponseException, чтобы справиться с любым типом ошибки.

Жрать 7 (из документов):

. \RuntimeException
└── TransferException (implements GuzzleException)
    └── RequestException
        ├── BadResponseException
        │   ├── ServerException
        │   └── ClientException
        ├── ConnectException
        └── TooManyRedirectsException

Итак, ваш код может выглядеть так:

try {
    $response = $client->request('GET', $url);
    if ($response->getStatusCode() >= 300) {
       // is HTTP status code (for non-exceptions) 
       $statusCode = $response->getStatusCode();
       // handle error 
    } else {
      // is valid URL
    }
            
} catch (TooManyRedirectsException $e) {
    // handle too many redirects
} catch (ClientException | ServerException $e) {
    // ClientException - A GuzzleHttp\Exception\ClientException is thrown for 400 level errors if the http_errors request option is set to true.
    // ServerException - A GuzzleHttp\Exception\ServerException is thrown for 500 level errors if the http_errors request option is set to true.
    if ($e->hasResponse()) {
       // is HTTP status code, e.g. 500 
       $statusCode = $e->getResponse()->getStatusCode();
    }
} catch (ConnectException $e) {
    // ConnectException - A GuzzleHttp\Exception\ConnectException exception is thrown in the event of a networking error. This may be any libcurl error, including certificate problems
    $handlerContext = $e->getHandlerContext();
    if ($handlerContext['errno'] ?? 0) {
       // this is the libcurl error code, not the HTTP status code!!!
       // for example 6 for "Couldn't resolve host"
       $errno = (int)($handlerContext['errno']);
    } 
    // get a description of the error (which will include a link to libcurl page)
    $errorMessage = $handlerContext['error'] ?? $e->getMessage();
         
} catch (\Exception $e) {
    // fallback, in case of other exception
}

Если вам действительно нужно тело, вы можете получить его как обычно:

https://docs.guzzlephp.org/en/stable/quickstart.html#using-responses

$body = $response->getBody();

person Sybille Peters    schedule 12.01.2021

Вы можете получить все сообщение об ошибке (без усечения). Пожалуйста, попробуйте следующий код:

try {
    ...
} catch (GuzzleHttp\Exception\RequestException $e) {
    $error = \GuzzleHttp\Psr7\str($e->getResponse());
    print_r($error);
}
person Rafael Xavier    schedule 31.05.2021

Для меня это работало с Guzzle внутри пакета Laravel:

try {
    $response = $this->client->get($url);
}
catch(\Exception $e) {
    $error = $e->getResponse();
    dd($error);
}
person shasi kanth    schedule 07.07.2021