【问题标题】:Handle Guzzle exception and get HTTP body处理 Guzzle 异常并获取 HTTP 正文
【发布时间】:2013-11-13 21:55:43
【问题描述】:

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

【问题讨论】:

标签: php guzzle


【解决方案1】:

Guzzle 6.x

根据the docs,您可能需要捕获的异常类型是:

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

【讨论】:

  • 对我来说$response->getBody()->getContents() 会返回一个空字符串。然后我在the docs 中偶然发现了这一点:\GuzzleHttp\Psr7\str($e->getResponse()) 将响应转换为 Psr7 字符串给我一个格式良好且完整的错误消息。
  • @AndyPlace 在看了 PSR 7 之后(在我写这个答案的时候我链接到的文档部分没有引用它,但是现在)它不是很明显对我来说,为什么调用Psr7\str() 会产生与->getContents() 不同的结果。你有一个最小的例子来证明这一点,这可能让我理解这一点并可能更新这个答案?
  • 值得一提的是,'http_errors' => false 选项可以在 Guzzle 请求中传递,从而禁用抛出异常。然后,无论状态码是什么,您都可以使用$response->getBody() 获取正文,如果需要,您可以使用$response->getStatusCode() 测试状态码。
  • 作为@AndyPlace,$response->getBody()->getContents() 在一种情况下给了我一个空字符串,我不明白为什么。但是使用 \GuzzleHttp\Psr7\str() 将所有 HTTP 响应作为字符串返回,而我只会返回 HTTP 正文。正如documentation 中所说,可以通过将主体转换为字符串来使用主体。 $stringBody = (string) $clientException->getResponse()->getBody();
  • 这对我有用,虽然我得到的是 \GuzzleHttp\Exception\RequestException 而不是返回 400 状态码。尝试 { $request->api('POST', 'endpoint.json'); } catch (RequestException $e) { print_r($e->getResponse()->getBody()->getContents()); }
【解决方案2】:

Guzzle 3.x

根据the docs,您可以捕获适当的异常类型(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类的实例。

【讨论】:

    【解决方案3】:

    虽然上面的答案很好,但它们不会捕获网络错误。正如 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);
      }
    
    }
    

    【讨论】:

    • JsonResponse 来自 Guzzle 的课程吗?
    • JsonResponse 来自 Symfony
    【解决方案4】:

    截至 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\RequestExceptiontheir exceptions docs 中提到的任何其他人。

    【讨论】:

    • 在处理异常时不应调用$response 对象上的方法,除非您已检查$e->hasResponse(),否则$response 可能是null,任何方法调用都会导致致命错误。
    • @pwaring,真的。正如 Guzzle 异常文档所说的那样。更新了答案。谢谢。
    • ...但修复后仍然存在问题。您正在捕获所有异常,而不仅仅是 Guzzle 异常,然后您在结果上调用 $e->hasResponse,当然,非 Guzzle 异常不存在这种方法。所以如果你从theMethodMayThrowException() 抛出一个非 Guzzle 异常,这段代码会捕获它,尝试调用一个不存在的方法,并因为不存在的方法而崩溃,有效地隐藏了错误的真正原因。最好是捕捉GuzzleHttp\Exception\RequestException 而不是Exception 以避免这种情况。
    【解决方案5】:

    如果将'http_errors' => false 放在guzzle 请求选项中,那么它会在收到4xx 或5xx 错误时停止抛出异常,如下所示:$client->get(url, ['http_errors' => false])。然后你解析响应,不管它是好的还是错误的,它都会在响应中 for more info

    【讨论】:

    • 这个问题是关于处理错误不要求停止错误异常
    • @Dlk 我认为您误解了答案。该方法允许您手动处理响应,例如,自己检查 HTTP 代码并适当地处理带有 HTTP 错误代码的响应。
    【解决方案6】:

    问题是:

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

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

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

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

    另请参阅"Working with errors" 上的官方文档中的此页面,其中指出:

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

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

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

        异常应该是一个有 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...
        }
        

        【讨论】:

          【解决方案9】:

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

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

          【讨论】:

            【解决方案10】:

            对我来说,这适用于 Laravel 包中的 Guzzle:

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

            【讨论】:

              猜你喜欢
              • 2020-05-11
              • 1970-01-01
              • 1970-01-01
              • 2019-10-19
              • 1970-01-01
              • 2018-07-20
              • 2020-02-07
              • 2018-02-06
              • 1970-01-01
              相关资源
              最近更新 更多