在PHP中处理Soap超时

时间:2009-05-07 14:59:40

标签: php web-services soap error-handling

我正在开发一个项目,我正在使用SOAP Web服务验证来自用户的信息。我目前正在处理错误,假设我收到来自Web服务的响应,但还需要处理服务超时或不可用的边缘情况。

如果超时或服务不可用,我需要假装请求成功(Web服务批准了该信息),但我不清楚会抛出什么异常。

一些伪代码:

// $client is PHP's SoapClient class
try {
  $response = $client->SomeSoapRequest();
}
catch(SoapFault $e){
  // handle issues returned by the web service
}
catch(Exception $e){
  // handle PHP issues with the request
}

我似乎无法找到的是:

  1. 超时是SoapFault吗?如果是这样,区分超时错误和Web服务问题(如类型错误等)的最佳方法是什么?我发现有一个页面提到了一个错误,其中消息是“错误加载标题”的结果,但没有提到这是否是Soap错误。
  2. 服务不可用性如何可能发生? PHP异常似乎有意义(从Web服务返回SoapFault,其中不可用性是套接字问题或类似问题)?
  3. 是否有可以测试超时的现有服务(例如示例)?大多数与超时相关的讨论似乎与通过扩展默认超时设置来防止超时有关,这在这种情况下并不理想。

8 个答案:

答案 0 :(得分:32)

1)如果超时,PHP会抛出一个带有faultcode="HTTP"faultstring="Error Fetching http headers"的SoapFault异常。

2)在我看来,区分超时错误和Web服务问题的最佳方法是查看SoapFault classfaultcodefaultstring成员。
特别是,faultcode元素旨在供软件使用,以提供用于识别故障的算法机制 您也可以阅读comment of the PHP manual,但没有方法可以阅读faultcode属性,因此您必须直接访问它(例如。$e->faultcode),因为getCode() }方法不起作用 SOAP 1.1 Specfaultcode字段定义了四个可能的值:

  • VersionMismatch :处理方发现SOAP Envelope元素的名称空间无效
  • MustUnderstand :SOAP Header元素的直接子元素,处理方未理解或未遵守,包含值为“1”的SOAP mustUnderstand属性
  • 客户端:客户端类错误表示邮件未正确形成或未包含相应信息才能成功。例如,消息可能缺少正确的身份验证或支付信息。这通常表明不应该在没有变化的情况下重新发送消息。
  • 服务器:服务器类错误表示无法处理消息的原因不是直接归因于消息本身的内容,而是处理消息。例如,处理可以包括与没有响应的上游处理器通信。该消息可能会在以后的某个时间点成功。

除了这些代码之外,PHP还使用HTTP代码来识别协议级别发生的错误(例如:套接字错误);例如,如果您在ext/soap/php_http.c源代码中搜索add_soap_fault,则可以看到生成某些类型的错误。
通过在PHP SOAP扩展源文件中搜索add_soap_faultsoap_server_fault函数,我构建了以下PHP SoapFault例外列表:

HTTP
----
Unable to parse URL
Unknown protocol. Only http and https are allowed.
SSL support is not available in this build
Could not connect to host
Failed Sending HTTP SOAP request
Failed to create stream??
Error Fetching http headers
Error Fetching http body: No Content-Length: connection closed or chunked data
Redirection limit reached: aborting
Didn't recieve an xml document
Unknown Content-Encoding
Can't uncompress compressed response
Error build soap request


VersionMismatch
---------------
Wrong Version


Client
------
A SOAP 1.2 envelope can contain only Header and Body
A SOAP Body element cannot have non Namespace qualified attributes
A SOAP Envelope element cannot have non Namespace qualified attributes
A SOAP Header element cannot have non Namespace qualified attributes
Bad Request
Body must be present in a SOAP envelope
Can't find response data
DTD are not supported by SOAP
encodingStyle cannot be specified on the Body
encodingStyle cannot be specified on the Envelope
encodingStyle cannot be specified on the Header
Error cannot find parameter
Error could not find "location" property
Error finding "uri" property
looks like we got "Body" with several functions call
looks like we got "Body" without function call
looks like we got no XML document
looks like we got XML without "Envelope" element
Missing parameter
mustUnderstand value is not boolean
SoapClient::__doRequest() failed
SoapClient::__doRequest() returned non string value
Unknown Data Encoding Style
Unknown Error
DataEncodingUnknown


MustUnderstand
--------------
Header not understood


Server
------
Couldn't find WSDL
DTD are not supported by SOAP
Unknown SOAP version
WSDL generation is not supported yet

3)要模拟超时条件,请尝试使用以下代码:

<强> soapclient.php

<?php

ini_set('default_socket_timeout', 10);

$client = new SoapClient(null, 
  array(
    'location' => "http://localhost/soapserver.php",
    'uri'      => "http://localhost/soapserver.php",
    'trace'    => 1
  )
);

try {
    echo $return = $client->__soapCall("add",array(41, 51));
} catch (SoapFault $e) {
    echo "<pre>SoapFault: ".print_r($e, true)."</pre>\n";
    //echo "<pre>faultcode: '".$e->faultcode."'</pre>";
    //echo "<pre>faultstring: '".$e->getMessage()."'</pre>";
}

?>

<强> soapserver.php

<?php

function add($a, $b) {
  return $a + $b;
}

sleep(20);

$soap = new SoapServer(null, array('uri' => 'http://localhost/soapserver.php'));
$soap->addFunction("add");
$soap->handle();

?>

请注意sleep脚本中的SoapServer.php调用,其时间(20)的长度超过default_socket_timeout脚本中为SoapClient.php参数指定的时间(10)。
如果要模拟服务不可用,可以将location协议从http更改为https脚本中的soapclient.php,假设您的Web服务器未配置用于SSL;通过这样做,PHP应该抛出“无法连接到主机”SoapFault。

答案 1 :(得分:7)

在通过HTTPS进行SOAP调用时,似乎没有考虑default_socket_timeout

撰写本文时漏洞已经公开。正如罗伯特·路德威克在一篇删除的答案Timing Out PHP Soap Calls (21 Oct 2009; by Published by Robert F. Ludwick)中引用的博客文章评论所指出的那样,该帖子讨论的变通方法(用卷曲请求覆盖SoapClient::__doRequest())也可以解决这个问题。

另一个相关的错误是:


博客文章中提到的代码经过了一些更改,可以在Github上找到支持HTTP身份验证的最新形式:

在任何情况下,不再需要解决方法,因为PHP SOAPClient扩展中已修复此问题。

答案 2 :(得分:3)

处理服务中的超时

$client = new SoapClient($wsdl, array("connection_timeout"=>10));

// SET SOCKET TIMEOUT
if(defined('RESPONSE_TIMEOUT') &&  RESPONSE_TIMEOUT != '') {
 ini_set('default_socket_timeout', RESPONSE_TIMEOUT);
}

答案 3 :(得分:2)

根据我的经验,如果$e->getMessage是“Error Fetching http headers”,那么您正在处理网络超时。

如果$e->getMessage类似于“无法连接到主机”,则您尝试访问的服务已关闭。

然后有“看起来我们没有XML文档”,这更加神秘,可能意味着不同的东西。

答案 4 :(得分:1)

我使用两个因素来获取SoapClient扩展以抛出一个很好的异常。消息和请求返回的时间。我认为错误消息“Error Fetching http headers”也可能在其他一些情况下出现,因此需要时间检查。

以下代码应该是正确的

class SoapClientWithTimeout extends SoapClient {
    public function __soapCall ($params, ---) {
        $time_start = microtime(true);
        try {
            $result = parent::__soapCall ($params, ---);
        }
        catch (Exception $e) {
            $time_request = (microtime(true)-$time_start);
            if(
                $e->getMessage() == 'Error Fetching http headers' &&
                ini_get('default_socket_timeout') < $time_request
            ) {
                throw new SoapTimeoutException(
                    'Soap request most likly timed out.'.
                    ' It took '.$time_request.
                    ' and the limit is '.ini_get('default_socket_timeout')
                );
            }

            // E: Not a timeout, let's rethrow the original exception
            throw $e;
        }

        // All good, no exception from the service or PHP
        return $result;
    }
}

class SoapTimeoutException extends Exception {}

然后我使用SoapClientWithTimeout

$client = new SoapClientWithTimeout();
try {
    $response = $client->SomeSoapRequest();
    var_dump($response);
}
catch(SoapTimeoutException $e){
    echo 'We experienced a timeout! '. $e->getMessage();
}
catch(Exception $e) {
    echo 'Exception: '.$e->getMessage();
}

调试服务超时。在调用服务之前添加以下行

ini_set('default_socket_timeout', 1);

答案 5 :(得分:0)

猜猜我迟到了,但万一有人还在寻找解决PHP soap客户端超时问题的方法 - 这就是对我有用的东西:

基本上用设置超时的cURL替换PHP SoapClient。请记住,有时WS需要在HTTP标头中指定的操作。在该网站上发布的原始解决方案不包括(检查评论)。

答案 6 :(得分:0)

仅通过ini全局设置default_socket_timeout可能无法达到您想要的效果。这会影响SOAP请求,但也会影响其他传出连接,包括数据库连接。相反,重写SoapClient的__doRequest()方法以自己建立HTTP连接。然后,您可以在套接字上设置自己的超时,检测它,并抛出可以捕获和处理的异常。

class SoapClientWithTimeout extends SoapClient {

    public function __construct ($wsdl, $options = null) {
        if (!$options) $options = [];

        $this->_connectionTimeout =
            @$options['connection_timeout']
            ?: ini_get ('default_socket_timeout');
        $this->_socketTimeout =
            @$options['socket_timeout']
            ?: ini_get ('default_socket_timeout');
        unset ($options['socket_timeout']);

        parent::__construct($wsdl, $options);
    }

    /**
     * Override parent __doRequest to add a timeout.
     */
    public function __doRequest (
        $request, $location, $action, $version, $one_way = 0
    ) {
        // Extract host, port, and scheme.
        $url_parts = parse_url ($location);
        $host = $url_parts['host'];
        $port =
            @$url_parts['port']
            ?: ($url_parts['scheme'] == 'https' ? 443 : 80);
        $length = strlen ($request);

        // Form the HTTP SOAP request.
        $http_req = "POST $location HTTP/1.0\r\n";
        $http_req .= "Host: $host\r\n";
        $http_req .= "SoapAction: $action\r\n";
        $http_req .= "Content-Type: text/xml; charset=utf-8\r\n";
        $http_req .= "Content-Length: $length\r\n";
        $http_req .= "\r\n";
        $http_req .= $request;

        // Need to tell fsockopen to use SSL when requested.
        if ($url_parts['scheme'] == 'https')
            $host = 'ssl://'.$host;

        // Open the connection.
        $socket = @fsockopen (
            $host, $port, $errno, $errstr, $this->_connectionTimeout
        );
        if (!$socket)
            throw new SoapFault (
                'Client',
                "Failed to connect to SOAP server ($location): $errstr"
            );

        // Send the request.
        stream_set_timeout ($socket, $this->_socketTimeout);
        fwrite ($socket, $http_req);

        // Read the response.
        $http_response = stream_get_contents ($socket);

        // Close the socket and throw an exception if we timed out.
        $info = stream_get_meta_data ($socket);
        fclose ($socket);
        if ($info['timed_out'])
            throw new SoapFault (
                'Client',
                "HTTP timeout contacting $location"
            );

        // Extract the XML from the HTTP response and return it.
        $response = preg_replace (
            '/
                \A       # Start of string
                .*?      # Match any number of characters (as few as possible)
                ^        # Start of line
                \r       # Carriage Return
                $        # End of line
             /smx',
            '', $http_response
        );
        return $response;
    }

}

答案 7 :(得分:0)

只需使用“stream_context”为WSDL加载设置超时设置(您需要先设置SoapClient $ options ['connection_timeout']):

class SoapClient2 extends SoapClient
{
  public function __construct($wsdl, $options=null)
  {
    if(isset($options['connection_timeout']))
    {
      $s_options = array(
          'http' => array(
              'timeout' => $options['connection_timeout']
              )
          );
      $options['stream_context'] = stream_context_create($s_options);
    }
    parent::__construct($wsdl, $options);
  }
}