Handling Soap timeouts in PHP

2019-03-08 03:49发布

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.

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.

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.

8条回答
疯言疯语
2楼-- · 2019-03-08 04:04

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

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);
  }
}
查看更多
等我变得足够好
3楼-- · 2019-03-08 04:05

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);
}
查看更多
孤傲高冷的网名
4楼-- · 2019-03-08 04:05

Simply setting the default_socket_timeout globally 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.

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

}
查看更多
该账号已被封号
5楼-- · 2019-03-08 04:06

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

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.

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:

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

查看更多
Juvenile、少年°
6楼-- · 2019-03-08 04:19

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.

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

$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);
查看更多
Deceive 欺骗
7楼-- · 2019-03-08 04:25

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

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

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

查看更多
登录 后发表回答