我正在开发一个项目,我正在使用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
}
我似乎无法找到的是:
SoapFault
吗?如果是这样,区分超时错误和Web服务问题(如类型错误等)的最佳方法是什么?我发现有一个页面提到了一个错误,其中消息是“错误加载标题”的结果,但没有提到这是否是Soap错误。答案 0 :(得分:32)
1)如果超时,PHP会抛出一个带有faultcode="HTTP"
和faultstring="Error Fetching http headers"
的SoapFault异常。
2)在我看来,区分超时错误和Web服务问题的最佳方法是查看SoapFault class的faultcode
和faultstring
成员。
特别是,faultcode
元素旨在供软件使用,以提供用于识别故障的算法机制
您也可以阅读comment of the PHP manual,但没有方法可以阅读faultcode
属性,因此您必须直接访问它(例如。$e->faultcode
),因为getCode()
}方法不起作用
SOAP 1.1 Spec为faultcode
字段定义了四个可能的值:
除了这些代码之外,PHP还使用HTTP
代码来识别协议级别发生的错误(例如:套接字错误);例如,如果您在ext/soap/php_http.c源代码中搜索add_soap_fault
,则可以看到生成某些类型的错误。
通过在PHP SOAP扩展源文件中搜索add_soap_fault
和soap_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);
}
}