处理Guzzle异常并获取HTTP正文

dom*_*mos 103 php guzzle

当服务器返回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?
}
Run Code Online (Sandbox Code Playgroud)

$e->getMessage返回代码信息,但不返回HTTP响应的主体.我如何获得响应机构?

Mar*_*ery 202

Guzzle 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();
}
Run Code Online (Sandbox Code Playgroud)

  • 值得一提的是,'http_errors'=> false`选项可以在Guzzle请求中传递,该请求禁用抛出异常.然后,无论状态代码是什么,您都可以使用`$ response-> getBody()`来获取正文,如果需要,您可以使用`$ response-> getStatusCode()`来测试状态代码. (22认同)
  • 对我来说`$ response-> getBody() - > getContents()`会返回一个空字符串.然后我在[文档](https://github.com/guzzle/guzzle/blob/master/docs/quickstart.rst)中偶然发现了这个:`\ GuzzleHttp\Psr7\str($ e-> getResponse()) `将响应作为Psr7字符串转换为我得到了一个格式良好且完整的错误消息. (12认同)
  • @AndyPlace在看了一下PSR 7之后(我在编写这个答案的时候没有被我链接到的文档部分引用,但现在是这样)对我来说,为什么调用`Psr7\str并不是很明显()`对于` - > getContents()`会有不同的结果.你有一个最小的例子来证明这一点,这可能让我理解这一点,也许更新这个答案? (3认同)
  • 作为@ AndyPlace,`$ response-> getBody()-> getContents()`在一种情况下给了我一个空字符串,我不明白为什么。但是使用`\ GuzzleHttp \ Psr7 \ str()`将所有HTTP响应作为字符串返回,而我只会使用HTTP正文。如[文档](https://github.com/guzzle/guzzle/blob/master/docs/quickstart.rst#using-responses)中所述,可以通过将主体强制转换为字符串来使用它。$ stringBody =(字符串)$ clientException-> getResponse()-> getBody();` (2认同)

seb*_*bbo 67

Guzzle 3.x

根据文档,您可以捕获相应的异常类型(ClientErrorResponseException对于4xx错误)并调用其getResponse()方法来获取响应对象,然后调用getBody():

use Guzzle\Http\Exception\ClientErrorResponseException;

...

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

传递truegetBody函数表示您希望将响应主体作为字符串.否则你会得到它作为类的实例Guzzle\Http\EntityBody.


cha*_*hap 45

虽然上面的答案是好的,但它们不会处理网络错误,因为Mark提到BadResponseException只是ClientException和ServerException的超类.但RequestException也是BadRequestException的超类.这不仅会捕获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);
  }

}
Run Code Online (Sandbox Code Playgroud)


Sha*_*kaB 8

要是搁'http_errors' => false在狂饮请求选项,那么它将停止抛出异常而获取4XX或5XX错误,像这样:$client->get(url, ['http_errors' => false])。然后你解析响应,不管它是好的还是错误,它都会在响应中 获取更多信息


Val*_*Shi 6

As of 2019 here is what I elaborated from the answers above and Guzzle docs to handle the exception, get the response body, status code, message and the other sometimes valuable response items.

try {
    /**
     * We use Guzzle to make an HTTP request somewhere in the
     * following theMethodMayThrowException().
     */
    $result = theMethodMayThrowException();
} catch (\Exception $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()); // Message
        var_dump((string) $response->getBody()); // Body
        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. ...
Run Code Online (Sandbox Code Playgroud)

Voila. You get the response's information in conveniently separated items.

Side Notes:

With catch clause we catch the inheritance chain PHP root exception class \Exception as Guzzle custom exceptions extend it.

This approach may be useful for use cases where Guzzle is used under the hood like in Laravel or AWS API PHP SDK so you cannot catch the genuine Guzzle exception.

In this case, the exception class may not be the one mentioned in the Guzzle docs (e.g. GuzzleHttp\Exception\RequestException as the root exception for Guzzle).

So you have to catch \Exception instead but bear in mind it is still the Guzzle exception class instance.

Though use with care. Those wrappers may make Guzzle $e->getResponse() object's genuine methods not available. In this case, you will have to look at the wrapper's actual exception source code and find out how to get status, message, etc. instead of using Guzzle $response's methods.

If you call Guzzle directly yourself you can catch GuzzleHttp\Exception\RequestException or any other one mentioned in their exceptions docs with respect to your use case conditions.

  • 处理异常时,不应在“$response”对象上调用方法,除非您已检查“$e->hasResponse()”,否则“$response”可能为“null”,并且任何方法调用都将导致致命错误。 (2认同)
  • ...但是修复后仍然存在问题。您捕获所有异常,而不仅仅是 Guzzle 异常,然后您对结果调用 `$e->hasResponse`,当然,对于非 Guzzle 异常,该方法不存在。因此,如果您从“theMethodMayThrowException()”引发非 Guzzle 异常,此代码将捕获它,尝试调用不存在的方法,并因不存在的方法而崩溃,从而有效地隐藏了错误的真正原因。最好是捕获 `GuzzleHttp\Exception\RequestException` 而不是 `Exception` 来避免这种情况。 (2认同)

The*_*der 6

该异常应该是具有 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...
}
Run Code Online (Sandbox Code Playgroud)


小智 5

上述响应均不适用于没有正文但仍有一些描述文本的错误。对我来说,这是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!
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Syb*_*ers 5

问题是:

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

虽然您可以专门处理 4xx 或 5xx 状态代码,但实际上捕获所有异常并相应地处理结果是有意义的。

问题还在于,您是否只想处理错误或获取正文?我认为在大多数情况下,处理错误而不获取消息正文或仅在非错误的情况下获取正文就足够了。

我会查看文档以检查您的 Guzzle 版本如何处理它,因为这可能会改变:https ://docs.guzzlephp.org/en/stable/quickstart.html#exceptions

另请参阅有关“处理错误”的官方文档中的此页面,其中指出:

收到 4xx 或 5xx 响应的请求将抛出一个Guzzle\Http\Exception\BadResponseException. 更具体地说,4xx错误会抛出 a Guzzle\Http\Exception\ClientErrorResponseException,而5xx错误会抛出 Guzzle \Http\Exception\ServerErrorResponseException。您可以捕获特定的异常,也可以只捕获BadResponseException以处理任一类型的错误。

Guzzle 7(来自文档):

. \RuntimeException
??? TransferException (implements GuzzleException)
    ??? RequestException
        ??? BadResponseException
        ?   ??? ServerException
        ?   ??? ClientException
        ??? ConnectException
        ??? TooManyRedirectsException
Run Code Online (Sandbox Code Playgroud)

因此,您的代码可能如下所示:

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
}
Run Code Online (Sandbox Code Playgroud)

如果你真的需要尸体,你可以像往常一样取回它:

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

$body = $response->getBody();
Run Code Online (Sandbox Code Playgroud)