处理 Guzzle 异常并获取 HTTP 正文

当服务器返回4xx 和5xx 状态码时,我想处理来自 Guzzle 的错误。我有个请求:

$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 响应的正文。如何获得响应正文?

233411 次浏览

狂饮3.x

对于每个 那些文件,您可以捕获适当的异常类型(针对4xx 错误的 ClientErrorResponseException)并调用其 getResponse()方法以获取响应对象,然后对该对象调用 getBody():

use Guzzle\Http\Exception\ClientErrorResponseException;


...


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

true传递给 getBody函数表示希望以字符串形式获取响应正文。否则,您将得到它作为类 Guzzle\Http\EntityBody的实例。

狂饮6.x

根据 那些文件,您可能需要捕获的异常类型是:

  • 400级错误的 GuzzleHttp\Exception\ClientException
  • 500级错误的 GuzzleHttp\Exception\ServerException
  • 两者的 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();
}

虽然上面的答案很好,但是他们不会发现网络错误。正如 Mark 提到的,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);
}


}

截至2020年,这里是我从上面的答案和 狂饮博士中详细阐述的处理异常、获取响应体、状态代码、消息和其他有时有价值的响应项目。

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

这种方法对于在诸如 Laravel 或 AWS API PHP SDK 中秘密使用 Guzzle 的用例可能非常有用,因此您无法捕捉真正的 Guzzle 异常。

在这种情况下,异常类可能不是 Guzzledocs 中提到的那个(例如 GuzzleHttp\Exception\RequestException作为 Guzzle.cn 的根异常)。

因此,您必须捕获 \Exception,但是请记住,它仍然是 Guzzle 异常类实例。

但要小心使用。这些包装可能使狂饮 $e->getResponse()对象的真正的方法不可用。在这种情况下,您必须查看包装器的实际异常源代码,并找出如何获取状态、消息等,而不是使用 Guzzle$response的方法。

如果您自己直接调用 Guzzle,您可以根据您的用例条件捕获 GuzzleHttp\Exception\RequestException他们的例外情况文件中提到的任何其他用例。

如果将 'http_errors' => false放在暴饮请求选项中,那么它将停止抛出异常,而得到4xx 或5xx 错误,如下所示: $client->get(url, ['http_errors' => false])。然后解析响应,不管它是正确的还是错误的,它都会出现在响应中 了解更多信息

以上所有的回答都不是针对没有主体但仍然有一些描述性文本的错误。对我来说,这是 SSL certificate problem: unable to get local issuer certificate错误。所以我直接查看了代码,因为 doc 没有说太多,并且这样做了(在 狂饮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!
}
}
}

问题是:

当服务器返回4xx 和5xx 状态码时,我想处理来自 Guzzle 的错误

虽然您可以特别处理4xx 或5xx 状态代码,但在实践中,捕获所有异常并相应地处理结果是有意义的。

问题还在于,你是只想处理错误,还是只想得到尸体?我认为在大多数情况下,处理错误而不获取消息体或者只获取非错误情况下的消息体就足够了。

我会查看文档,以检查您的版本的狂饮处理它,因为这可能会改变: 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

因此,您的代码可能是这样的:

use GuzzleHttp\Exception\TooManyRedirectsException;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Exception\ConnectException;


// ...


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 is thrown for 400 level errors if the http_errors request option is set to true.
// 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 is thrown in the event of a networking error.
// This may be an error reported by lowlevel functionality
// (e.g.  cURL error)
$handlerContext = $e->getHandlerContext();
if ($handlerContext['errno'] ?? 0) {
// this is the lowlevel error code, not the HTTP status code!!!
// for example 6 for "Couldn't resolve host" (for libcurl)
$errno = (int)($handlerContext['errno']);
}
// get a description of the error
$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();

在底层,默认使用 cURL 或 PHP 流包装器,请参阅 狂饮博士,因此错误代码和消息可能反映:

Guzzle 不再需要 cURL 来发送 HTTP 请求。如果没有安装 cURL,Guzzle 将使用 PHP 流包装器发送 HTTP 请求。或者,您可以提供用于发送请求的自己的 HTTP 处理程序。请记住,发送并发请求仍然需要 cURL。


您可以得到整个错误消息(未截断)。 请尝试以下代码:

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

对我来说,这个工作与暴食在 Laravel 软件包:

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

异常应该是 BadResponseException 的一个实例,该实例具有 getResponse 方法。然后可以将响应主体强制转换为字符串。参考资料: https://github.com/guzzle/guzzle/issues/1105

use GuzzleHttp\Exception\BadResponseException;


$url = $this->baseUrl . "subnet?section=$section";
try {
$response = $this->client->get($url);
$subnets = json_decode($response->getBody(), true);
return $subnets['subnets'];
} catch (BadResponseException $ex) {
$response = $ex->getResponse();
$jsonBody = (string) $response->getBody();
// do something with json string...
}