SoapClient with HTTPS timeout

The examples above implement a minimal version of HTTPS with timeout. We can use this code in our own version of the SoapClient, so that we can do SOAP requests over HTTPS using the timeout functionality.

To do this, we extend the built-in SoapClient class to use our HTTP method instead of the default one. This can be done by overriding the __doRequest() method.

public string SoapClient::__doRequest ( string $request , string $location , string $action , int $version [, int $one_way= 0 ] )

The __doRequest method has the task of submitting the request to the remote server. This is exactly what we would like to do ourselves. It has the following parameters:

  • $request, the XML request to post to the remote location
  • $location, the URL to post the request to
  • $action, the SOAP action which we will send using the SoapAction header
  • $version, not used right now
  • $one_way, whether a response is expected from the server

The __doRequest method only handles actual SOAP requests, not the retrieving of the WSDL. There is no method which can be overridden to retrieve the WSDL using a timeout. If the WSDL does not change often, the easiest way is to retrieve it once, save it to disk and pass the filename to the SoapClient. If it changes often, you can retrieve it with the code shown above. This can be done automatically in the SoapClient constructor, so that it is transparent to users of the SoapClient.

Here is a SoapClient with our new HTTP implementation to do SOAP requests using a timeout:

class TimeoutSoapClient extends SoapClient
{
    const TIMEOUT = 4;

    public function __doRequest($request, $location, $action, $version, $one_way = 0)
    {
        $url_parts = parse_url($location);
        $host = $url_parts['host'];
        $http_req = 'POST '.$location.' HTTP/1.0'."\r\n";
        $http_req .= 'Host: '.$host."\r\n";
        $http_req .= 'SoapAction: '.$action."\r\n";
        $http_req .= "\r\n";
        $http_req .= $request;
        $port = 80;
        if ($url_parts['scheme'] == 'https')
        {
            $port = 443;
            $host = 'ssl://'.$host;
        }
        $socket = fsockopen($host, $port);
        fwrite($socket, $request);
        stream_set_blocking($socket, false);
        $response = '';
        $stop = microtime(true) + self::TIMEOUT;
        while (!feof($socket))
        {
            $response .= fread($socket, 2000);
            if (microtime(true) > $stop)
            {
                throw new SoapFault('Client', 'HTTP timeout');
            }
        }
        return $response;
    }
}

We parse the location URL to determine to which host to connect. Then, we construct a request specifying the Host header, which is required for HTTP, and the SoapAction header, which is required for SOAP. If HTTPS is used, we use the HTTPS port and prepend "ssl://" to the host to let fsockopen() know that we want a SSL connection. We write the request and listen for a response in non-blocking mode, as shown earlier.