【问题标题】:Catching exceptions from Guzzle从 Guzzle 中捕获异常
【发布时间】:2013-07-13 13:20:51
【问题描述】:

我正在尝试从我正在开发的 API 上运行的一组测试中捕获异常,并且我正在使用 Guzzle 来使用 API 方法。我已经将测试包装在 try/catch 块中,但它仍然抛出未处理的异常错误。如他们的文档中所述添加事件侦听器似乎没有任何作用。我需要能够检索 HTTP 代码为 500、401、400 的响应,实际上任何不是 200 的响应,因为如果它不起作用,系统将根据调用结果设置最合适的代码.

当前代码示例

foreach($tests as $test){

        $client = new Client($api_url);
        $client->getEventDispatcher()->addListener('request.error', function(Event $event) {        

            if ($event['response']->getStatusCode() == 401) {
                $newResponse = new Response($event['response']->getStatusCode());
                $event['response'] = $newResponse;
                $event->stopPropagation();
            }            
        });

        try {

            $client->setDefaultOption('query', $query_string);
            $request = $client->get($api_version . $test['method'], array(), isset($test['query'])?$test['query']:array());


          // Do something with Guzzle.
            $response = $request->send();   
            displayTest($request, $response);
        }
        catch (Guzzle\Http\Exception\ClientErrorResponseException $e) {

            $req = $e->getRequest();
            $resp =$e->getResponse();
            displayTest($req,$resp);
        }
        catch (Guzzle\Http\Exception\ServerErrorResponseException $e) {

            $req = $e->getRequest();
            $resp =$e->getResponse();
            displayTest($req,$resp);
        }
        catch (Guzzle\Http\Exception\BadResponseException $e) {

            $req = $e->getRequest();
            $resp =$e->getResponse();
            displayTest($req,$resp);
        }
        catch( Exception $e){
            echo "AGH!";
        }

        unset($client);
        $client=null;

    }

即使对于抛出的异常类型使用特定的 catch 块,我仍然会返回

Fatal error: Uncaught exception 'Guzzle\Http\Exception\ClientErrorResponseException' with message 'Client error response [status code] 401 [reason phrase] Unauthorized [url]

如您所料,页面上的所有执行都会停止。 BadResponseException 捕获的添加使我能够正确捕获 404,但这似乎不适用于 500 或 401 响应。谁能建议我哪里出错了。

【问题讨论】:

  • 这段代码是否在命名空间下?如果是这样,除非您 useing 异常,否则您可能需要在它们前面加上 `` 以明确声明 FQ 类。因此,例如,'\Guzzle\Http\Exception\ClientErrorResponseException'

标签: php api functional-testing guzzle


【解决方案1】:

如果您使用的是最新版本,比如 6^ 并且您有 JSON 参数,则可以将 'http_errors' => false 与 JSON 一起添加到数组中,如下所示

我一直在寻找这样做的方法,即在其中使用我的 JSON,但找不到直接的答案。

【讨论】:

    【解决方案2】:

    我想更新 Psr-7 Guzzle、Guzzle7 和 HTTPClient 中异常处理的答案(富有表现力,围绕 Laravel 提供的 Guzzle HTTP 客户端的最小 API)。

    Guzzle7(同样适用于 Guzzle 6)

    使用 RequestException,RequestException 捕获在传输请求时可能引发的任何异常。

    try{
      $client = new \GuzzleHttp\Client(['headers' => ['Authorization' => 'Bearer ' . $token]]);
      
      $guzzleResponse = $client->get('/foobar');
      // or can use
      // $guzzleResponse = $client->request('GET', '/foobar')
        if ($guzzleResponse->getStatusCode() == 200) {
             $response = json_decode($guzzleResponse->getBody(),true);
             //perform your action with $response 
        } 
    }
    catch(\GuzzleHttp\Exception\RequestException $e){
       // you can catch here 400 response errors and 500 response errors
       // You can either use logs here use Illuminate\Support\Facades\Log;
       $error['error'] = $e->getMessage();
       $error['request'] = $e->getRequest();
       if($e->hasResponse()){
           if ($e->getResponse()->getStatusCode() == '400'){
               $error['response'] = $e->getResponse(); 
           }
       }
       Log::error('Error occurred in get request.', ['error' => $error]);
    }catch(Exception $e){
       //other errors 
    }
    

    Psr7 狂饮

    use GuzzleHttp\Psr7;
    use GuzzleHttp\Exception\RequestException;
    
    try {
        $client->request('GET', '/foo');
    } catch (RequestException $e) {
        $error['error'] = $e->getMessage();
        $error['request'] = Psr7\Message::toString($e->getRequest());
        if ($e->hasResponse()) {
            $error['response'] = Psr7\Message::toString($e->getResponse());
        }
        Log::error('Error occurred in get request.', ['error' => $error]);
    }
    

    对于 HTTPClient

    use Illuminate\Support\Facades\Http;
    try{
        $response = Http::get('http://api.foo.com');
        if($response->successful()){
            $reply = $response->json();
        }
        if($response->failed()){
            if($response->clientError()){
                //catch all 400 exceptions
                Log::debug('client Error occurred in get request.');
                $response->throw();
            }
            if($response->serverError()){
                //catch all 500 exceptions
                Log::debug('server Error occurred in get request.');
                $response->throw();
            }
            
        }
     }catch(Exception $e){
         //catch the exception here
     }
    
    

    【讨论】:

      【解决方案3】:

      在我的例子中,我将 Exception 扔到一个命名空间文件上,所以 php 试图捕捉 My\Namespace\Exception 因此根本没有捕捉到任何异常。

      值得检查 catch (Exception $e) 是否找到了正确的 Exception 类。

      试试catch (\Exception $e)(那里有\)看看它是否有效。

      【讨论】:

      • 我希望我第一次遇到同样的问题时向下滚动到这个错误。对我来说,我使用的是过时的 Guzzle Exception 名称并且没有捕获通用异常,因为我不在根 Namesapce 中。在 Exception 开始捕获通用 Exception 之前添加反斜杠允许我在更具体的 Guzzle Exceptions 上查看我的名称不匹配错误。请参阅stackoverflow.com/a/7892917/2829359 上的 cmets。
      • 这也是我遇到的确切问题。好答案
      【解决方案4】:

      老问题,但 Guzzle 在异常对象中添加了响应。因此,对GuzzleHttp\Exception\ClientException 进行简单的try-catch,然后在该异常上使用getResponse 以查看400 级错误并从那里继续。

      【讨论】:

        【解决方案5】:

        按照@dado 的建议,我正在捕捉GuzzleHttp\Exception\BadResponseException。但是有一天,当域的 DNS 不可用时,我收到了GuzzleHttp\Exception\ConnectException。 所以我的建议是 - 捕获 GuzzleHttp\Exception\ConnectException 以确保 DNS 错误的安全。

        【讨论】:

        • 听起来你应该捕捉到GuzzleHttp\Exception\RequestException,它是ConnectExceptionBadResponseExceptionTooManyRedirectsException的父级。
        【解决方案6】:

        根据您的项目,可能需要禁用 guzzle 异常。有时编码规则不允许流控制例外。您可以像这样禁用 Guzzle 3 的例外:

        $client = new \Guzzle\Http\Client($httpBase, array(
          'request.options' => array(
             'exceptions' => false,
           )
        ));
        

        这不会因为超时等原因禁用 curl 异常,但现在您可以轻松获取每个状态代码:

        $request = $client->get($uri);
        $response = $request->send();
        $statuscode = $response->getStatusCode();
        

        要检查,如果你有一个有效的代码,你可以使用这样的东西:

        if ($statuscode > 300) {
          // Do some error handling
        }
        

        ...或更好地处理所有预期的代码:

        if (200 === $statuscode) {
          // Do something
        }
        elseif (304 === $statuscode) {
          // Nothing to do
        }
        elseif (404 === $statuscode) {
          // Clean up DB or something like this
        }
        else {
          throw new MyException("Invalid response from api...");
        }
        

        对于 Guzzle 5.3

        $client = new \GuzzleHttp\Client(['defaults' => [ 'exceptions' => false ]] );
        

        感谢@mika

        对于 Guzzle 6

        $client = new \GuzzleHttp\Client(['http_errors' => false]);
        

        【讨论】:

        • 曾经遇到过由于缺少break 导致的奇怪错误;-) 但是,如果您有多个必须以相同方式处理的状态代码,这将是一个很好的解决方案。我更喜欢if,因为switch只支持==
        • 感谢您提及request.options。解决了我的问题并让我正确查找它。 :)
        • 或者在 Guzzle5.3 中: $client = new \GuzzleHttp\Client(['defaults' => [ 'exceptions' => false ]] );
        • 这使我的培根免于一项紧急项目。谢谢 Trendfischer 和 SO!
        【解决方案7】:

        您需要使用 http_errors => false 添加一个额外的参数

        $request = $client->get($url, ['http_errors' => false]);
        
        【解决方案8】:

        要捕获 Guzzle 错误,您可以执行以下操作:

        try {
            $response = $client->get('/not_found.xml')->send();
        } catch (Guzzle\Http\Exception\BadResponseException $e) {
            echo 'Uh oh! ' . $e->getMessage();
        }
        

        ...但是,为了能够“记录”或“重新发送”您的请求,请尝试以下操作:

        // Add custom error handling to any request created by this client
        $client->getEventDispatcher()->addListener(
            'request.error', 
            function(Event $event) {
        
                //write log here ...
        
                if ($event['response']->getStatusCode() == 401) {
        
                    // create new token and resend your request...
                    $newRequest = $event['request']->clone();
                    $newRequest->setHeader('X-Auth-Header', MyApplication::getNewAuthToken());
                    $newResponse = $newRequest->send();
        
                    // Set the response object of the request without firing more events
                    $event['response'] = $newResponse;
        
                    // You can also change the response and fire the normal chain of
                    // events by calling $event['request']->setResponse($newResponse);
        
                    // Stop other events from firing when you override 401 responses
                    $event->stopPropagation();
                }
        
        });
        

        ... 或者如果您想“停止事件传播”,您可以覆盖事件侦听器(优先级高于 -255)并简单地停止事件传播。

        $client->getEventDispatcher()->addListener('request.error', function(Event $event) {
        if ($event['response']->getStatusCode() != 200) {
                // Stop other events from firing when you get stytus-code != 200
                $event->stopPropagation();
            }
        });
        

        这是一个好主意,可以防止出现以下错误:

        request.CRITICAL: Uncaught PHP Exception Guzzle\Http\Exception\ClientErrorResponseException: "Client error response
        

        在您的应用程序中。

        【讨论】:

        • 这在 Guzzle 6 中不再可能。知道如何使用中间件做到这一点吗?
        【解决方案9】:

        如果在 try 块中抛出异常,那么在最坏的情况下,Exception 应该会捕获任何未捕获的东西。

        考虑测试的第一部分是抛出异常并将其包装在 try 块中。

        【讨论】:

        • 你是对的,在抛出异常的 try/catch 之外有一个测试。愚蠢的错误,感谢您的帮助。
        猜你喜欢
        • 2016-10-31
        • 2017-03-27
        • 2020-10-23
        • 1970-01-01
        • 1970-01-01
        • 2016-10-05
        • 2017-08-02
        • 2015-03-20
        • 1970-01-01
        相关资源
        最近更新 更多