在 PHP 中处理 Soap 超时

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/835184/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 00:01:51  来源:igfitidea点击:

Handling Soap timeouts in PHP

phpweb-servicessoaperror-handling

提问by Rob

I'm working on a project where I am verifying information from a user with a SOAP web service. I currently am taking care of errors assuming that I'm receiving responses from the web service, but also need to handle the edge cases of a service timeout or unavailability.

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

In the case of a timeout or service unavailability, I need to pretend that the request was successful (that the web service approved the info), but I'm not clear on what exceptions are thrown.

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

Some pseudo-code:

一些伪代码:

// $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
}

What I can't seem to find is:

我似乎找不到的是:

  1. Are timeouts a SoapFault? If so, what is the best way to distinguish between a timeout error and web service issues (like a type error, etc.)? I found one page that mentioned an error where the message was something to the effect of "Error loading headers", but didn't mention if this was a Soap fault.
  2. How is a service unavailability potentially going to happen? A PHP exception seems like it would make sense (a SoapFault would be returned from the web service where unavailability would be a socket issue or similar)?
  3. Is there an existing service (e.g. example) that I can test a timeout against? Most timeout related discussions seem to be related to preventing timeouts by extending the default timeout setting, which isn't ideal in this situation.
  1. 超时是SoapFault? 如果是这样,区分超时错误和 Web 服务问题(如类型错误等)的最佳方法是什么?我发现一个页面提到了一个错误,其中消息是“错误加载标题”的影响,但没有提到这是否是 Soap 错误。
  2. 服务不可用是如何发生的?PHP 异常似乎有意义(将从 Web 服务返回 SoapFault,其中不可用是套接字问题或类似问题)?
  3. 是否有我可以测试超时的现有服务(例如示例)?大多数与超时相关的讨论似乎都与通过扩展默认超时设置来防止超时有关,这在这种情况下并不理想。

回答by Rob

1) In case of timeout, PHP throws a SoapFault exception with faultcode="HTTP"and faultstring="Error Fetching http headers".

2) In my opinion, the best way to distinguish between a timeout error and web service issues is by looking at the faultcodeand faultstringmembers of the SoapFault class.
In particular, the faultcodeelement is intended for use by software to provide an algorithmic mechanism for identifying the fault.
As you can also read in a comment of the PHP manual, there is no method to read the faultcodeproperty, so you have to access it directly (eg. $e->faultcode), because the getCode()method does not work.
The SOAP 1.1 Specdefines four possible values for the faultcodefield:

1) 在超时的情况下,PHP 会抛出一个带有faultcode="HTTP"和的 SoapFault 异常faultstring="Error Fetching http headers"

2) 在我看来,区分超时错误和 Web 服务问题的最佳方法是查看SoapFault 类faultcodefaultstring成员。 特别是,该元素旨在供软件使用,以提供识别故障的算法机制。 正如您在 PHP 手册注释中也看到的,没有读取属性的方法,因此您必须直接访问它(例如),因为该方法不起作用。 的SOAP 1.1规格定义了四个可能的值字段:
faultcode
faultcode$e->faultcodegetCode()
faultcode

  • VersionMismatch: The processing party found an invalid namespace for the SOAP Envelope element
  • MustUnderstand: An immediate child element of the SOAP Header element that was either not understood or not obeyed by the processing party contained a SOAP mustUnderstand attribute with a value of "1"
  • Client: The Client class of errors indicate that the message was incorrectly formed or did not contain the appropriate information in order to succeed. For example, the message could lack the proper authentication or payment information. It is generally an indication that the message should not be resent without change.
  • Server: The Server class of errors indicate that the message could not be processed for reasons not directly attributable to the contents of the message itself but rather to the processing of the message. For example, processing could include communicating with an upstream processor, which didn't respond. The message may succeed at a later point in time.
  • VersionMismatch:处理方发现 SOAP Envelope 元素的命名空间无效
  • MustUnderstand:处理方不理解或不遵守的 SOAP Header 元素的直接子元素包含值为“1”的 SOAP mustUnderstand 属性
  • Client:Client 类错误表明消息格式不正确或不包含正确的信息才能成功。例如,该消息可能缺少正确的身份验证或支付信息。这通常表示不应在不更改的情况下重新发送消息。
  • Server:Server 类错误表示无法处理消息的原因并非直接归因于消息本身的内容,而是归因于消息的处理。例如,处理可能包括与没有响应的上游处理器通信。该消息可能会在稍后的时间点成功。

In addition to those codes, PHP uses the HTTPcode for identifying the errors happening at the protocol level (eg.: socket errors); for example, if you search for add_soap_faultin the ext/soap/php_http.csource code you can see when some of these kind of faults are generated.
By searching for the add_soap_faultand soap_server_faultfunctions in the PHP SOAP extension source files, I've built the following list of PHP SoapFaultexceptions:

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

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) To simulate the timeout condition, try with the following code:

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

soapclient.php

肥皂客户端.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

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

?>

Notice the sleepcall in the SoapServer.phpscript with a time (20) longest than the time (10) specified for the default_socket_timeoutparameter in the SoapClient.phpscript.
If you want to simulate a service unavailability, you could for example change the locationprotocol from httpto httpsin the soapclient.phpscript, assuming that your web server is not configured for SSL; by doing this, PHP should throw a "Could not connect to host" SoapFault.

请注意脚本中sleep调用SoapServer.php的时间 (20) 比为脚本中的default_socket_timeout参数指定的时间 (10) 最长SoapClient.php
如果你想模拟服务不可用,例如,你可以改变location从协议httphttpssoapclient.php脚本,假设你的Web服务器没有为SSL配置; 通过这样做,PHP 应该抛出“无法连接到主机”的 SoapFault。

回答by tobych

Looks like default_socket_timeoutis not taken into account when making SOAP calls over HTTPS:

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

Bug open at the time of writing. As a comment on the blog post Robert Ludwick referenced in a deleted answer Timing Out PHP Soap Calls (21 Oct 2009; by Published by Robert F. Ludwick)points out, the workaround the post discusses (overriding SoapClient::__doRequest()with a curl request) works around this bug also.

在撰写本文时错误打开。正如 Robert Ludwick 在已删除的回答Timeing Out PHP Soap Calls(2009 年 10 月 21 日;由 Robert F. Ludwick 出版)中引用的博客文章的评论指出,该文章讨论的解决方法(覆盖SoapClient::__doRequest()curl 请求)解决了这个问题错误也。

Another related bug is:

另一个相关的错误是:



The code mentioned in the blog post has undergone some changes and can be found in it's latest form with support of HTTP authentication here on Github:

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

In any case, the workaround shouldn't be needed any longer as this problem has been fixed in the PHP SOAPClient extension.

在任何情况下,都不再需要该变通方法,因为该问题已在 PHP SOAPClient 扩展中修复。

回答by wosis

From my experience, if $e->getMessageis "Error Fetching http headers", you are dealing with a network timeout.

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

If $e->getMessageis something like "Cannot connect to host", the service you are trying to reach is down.

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

Then there is "Looks like we got no XML document", which is more cryptic an can mean different things.

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

回答by ToughPal

To deal with timeouts in the service

处理服务中的超时

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

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

回答by HNygard

I used two factors to get my SoapClient extention to throw a nice exception. The message and the time the request took to return. I think the error message "Error Fetching http headers" can also occure in some other cases, therefore the time check.

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

The following code should be about right

下面的代码应该是正确的

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 {}

I then use SoapClientWithTimeout

然后我使用 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();
}

To debug your service timing out. Add the following line before calling the service

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

ini_set('default_socket_timeout', 1);

回答by Maciej Ja?niaczyk

Guess I'm little late, but in case someone is still looking for solution to timeouts in php soap client - here's what's worked for me:

猜猜我来晚了,但如果有人仍在寻找解决 php soap 客户端超时的方法 - 这对我有用:

Basicly replacing PHP SoapClient with cURL with set timeout. Just keep in mind, sometimes WS expects action specified in HTTP header. Original solution posted on that website doesn't include that (check comments).

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

回答by Derek

Simply setting the default_socket_timeoutglobally via the ini may not do what you want. This would affect SOAP requests, but would also affect other outgoing connections, including DB connections. Instead, override SoapClient's __doRequest() method to make the HTTP connection yourself. You can then set your own timeout on the socket, detect it, and throw exceptions that you can trap and handle.

简单地default_socket_timeout通过 ini设置全局可能不会做你想要的。这会影响 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;
    }

}

回答by sh3b4ng

just use the "stream_context" to set the timeout setting also for WSDL loading (you need to set the SoapClient $options['connection_timeout'] before):

只需使用“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);
  }
}