处理 Guzzle 异常并获取 HTTP 正文


Handle Guzzle exception and get HTTP body

我想在服务器返回 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 响应的正文。如何获取响应正文?

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

Guzzle 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的实例。

虽然上面的答案很好,但它们不会捕获网络错误。正如 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 年,这是我从上面的答案和 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异常类实例。

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

如果您自己直接致电 Guzzle,您可以捕获GuzzleHttp'Exception'RequestException或他们的异常文档中提到的与您的用例条件有关

的任何其他人。

如果将'http_errors' => false放入guzzle请求选项中,那么它将在收到4xx或5xx错误时停止抛出异常,如下所示:$client->get(url, ['http_errors' => false]) . 然后你解析响应,不管它没问题还是错误,它都会在响应中欲了解更多信息

问题是:

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

虽然您可以专门处理 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来处理任一类型的错误。

Guzzle 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 文档,因此错误代码和消息可能反映:

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

<小时 />
  • 咕噜咕噜的例外
  • 库库尔错误代码
  • HTTP 状态代码

异常应该是具有getResponse方法的BadResponseException的实例。然后,可以将响应正文强制转换为字符串。参考: 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...
}

上述响应都不适用于没有正文但仍有一些描述文本的错误。对我来说,这是SSL certificate problem: unable to get local issuer certificate错误。所以我直接研究了代码,因为 doc 并没有说太多,并且这样做了(在 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!
        }
    }
}

对我来说,这在Laravel软件包中与Guzzle一起工作:

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

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

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