ZF-9125: connection_timeout option not supported in Zend_Soap_Client

Description

The code to implement a connection timeout is present but commented out in the Zend_Soap_Client.php file; support for connection timeout seems to work in the PHP Soap client - can this code be enabled?

Comments

Same question here. How can a timeout be enabled for Zend_Soap_Client?

Bumping the priority on this. Is there any explanation as to why the connection timeout is explicitly ignored in Zend_Soap_Client? The soap library supports it. It's trivially simple to enable, but this gives me pause:


// Not used now
// case 'connection_timeout':
//     $this->_connection_timeout = $value;
//    break;

Why is this not used?

Bump. Also, it will be cool to have full support of timeout, I mean this note for SoapClient options. {quote} The connection_timeout option defines a timeout in seconds for the connection to the SOAP service. This option does not define a timeout for services with slow responses. To limit the time to wait for calls to finish the default_socket_timeout setting is available. {quote} And some other timeout features. I guess this information may be helpful http://darqbyte.com/2009/10/…

This issue still exists in Zend Framework 1.11.6. Does anybody know the reason why this is not fixed?

No idea, but just to report the result of my investigations:

  1. git history is useless. These lines of code seem not to have been changed since the initial check-in to git: https://github.com/zendframework/zf2/… commit e443b8c12a05ad49b9a583e17f1a08b9db2be267 0 parents Showing 6,457 changed files with 1,410,220 additions and 0 deletions.

  2. Subversion is more helpful. This is the commit that changed this: http://framework.zend.com/code/revision.php/…. Argh, no, that is an irrelevant change ZF-4118.

  3. And the previous change before that is http://framework.zend.com/code/revision.php/… / http://framework.zend.com/code/diff.php/… which is where the damage was really done. Commit message is "Zend_Soap_Client improvements" so that is nice and informative . No mention of any bug number.

  4. This blog post explains the real issue: http://darqbyte.com/2009/10/…. Basically the SOAP connection_timeout does not actually do what you want. If you really want time-outs to work you have to do ini_set('default_socket_timeout', $timeout), but that is a global setting, so that is no good if you want time-outs on a per-client-class basis. The blog post also gives a possible solution, but it depends on curl.

I hope that information is of some help.

After doing some googling on this issue including the very helpfull post at http://darqbyte.com/2009/10/… I have written a small but working version of this work around.


class Tc_Soap_Client extends Zend_Soap_Client {

    function setConnectionTimeout($timeout) {
        $this->_connection_timeout = $timeout;
    }

    function getOptions() {
        $options = parent::getOptions();
        if (is_integer($this->_connection_timeout)) {
            $options["connection_timeout"] = $this->_connection_timeout;
        }
        return $options;
    }

    public function _doRequest(Zend_Soap_Client_Common $client, $request, $location, $action, $version, $one_way = null) {
        if (!is_integer($this->_connection_timeout)) {
            return parent::_doRequest($client, $request, $location, $action, $version, $one_way);
        } else {
            $curl = curl_init($location);
            curl_setopt($curl, CURLOPT_VERBOSE, FALSE);
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
            curl_setopt($curl, CURLOPT_POST, TRUE);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $request);
            curl_setopt($curl, CURLOPT_HEADER, FALSE);
            curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-Type: text/xml"));
            curl_setopt($curl, CURLOPT_TIMEOUT, $this->_connection_timeout);
            $password = $this->getHttpPassword();
            if ($password) {
                curl_setopt($curl, CURLOPT_USERPWD, "{$this->getHttpLogin()}:$password");
                curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
            }
            $response = curl_exec($curl);
            if (curl_errno($curl)) {
                throw new Exception(curl_error($curl));
            }
            curl_close($curl);
            if (!$one_way) {
                return ($response);
            }
        }
    }
}

This could further be refined to just take the https calls and redirect them to curl as socket timeout in php seem to be a problem just for https. Hope this helps...

Bump. I had to extend Zend_Soap_Client to a custom class, as such:


<?php
class Application_Soap_Client extends Zend_Soap_Client {
    
    /**
     * Set Options
     *
     * Allows setting options as an associative array of option => value pairs.
     *
     * @param  array|Zend_Config $options
     * @return Zend_Soap_Client
     * @throws Zend_SoapClient_Exception
     */
    public function setOptions($options)
    {
        if($options instanceof Zend_Config) {
            $options = $options->toArray();
        }
    
        foreach ($options as $key => $value) {
            switch ($key) {
                case 'classmap':
                case 'classMap':
                    $this->setClassmap($value);
                    break;
                case 'encoding':
                    $this->setEncoding($value);
                    break;
                case 'soapVersion':
                case 'soap_version':
                    $this->setSoapVersion($value);
                    break;
                case 'wsdl':
                    $this->setWsdl($value);
                    break;
                case 'uri':
                    $this->setUri($value);
                    break;
                case 'location':
                    $this->setLocation($value);
                    break;
                case 'style':
                    $this->setStyle($value);
                    break;
                case 'use':
                    $this->setEncodingMethod($value);
                    break;
                case 'login':
                    $this->setHttpLogin($value);
                    break;
                case 'password':
                    $this->setHttpPassword($value);
                    break;
                case 'proxy_host':
                    $this->setProxyHost($value);
                    break;
                case 'proxy_port':
                    $this->setProxyPort($value);
                    break;
                case 'proxy_login':
                    $this->setProxyLogin($value);
                    break;
                case 'proxy_password':
                    $this->setProxyPassword($value);
                    break;
                case 'local_cert':
                    $this->setHttpsCertificate($value);
                    break;
                case 'passphrase':
                    $this->setHttpsCertPassphrase($value);
                    break;
                case 'compression':
                    $this->setCompressionOptions($value);
                    break;
                case 'stream_context':
                    $this->setStreamContext($value);
                    break;
                case 'features':
                    $this->setSoapFeatures($value);
                    break;
                case 'cache_wsdl':
                    $this->setWsdlCache($value);
                    break;
                case 'useragent':
                case 'userAgent':
                case 'user_agent':
                    $this->setUserAgent($value);
                    break;
    
                case 'connection_timeout':
                    $this->_connection_timeout = $value;
                break;
    
                    default:
                        require_once 'Zend/Soap/Client/Exception.php';
                        throw new Zend_Soap_Client_Exception('Unknown SOAP client option');
                        break;
            }
        }
    
        return $this;
    }
    
    /**
     * Return array of options suitable for using with SoapClient constructor
     *
     * @return array
     */
    public function getOptions()
    {
        $options = array();
    
        $options['classmap']       = $this->getClassmap();
        $options['encoding']       = $this->getEncoding();
        $options['soap_version']   = $this->getSoapVersion();
        $options['wsdl']           = $this->getWsdl();
        $options['uri']            = $this->getUri();
        $options['location']       = $this->getLocation();
        $options['style']          = $this->getStyle();
        $options['use']            = $this->getEncodingMethod();
        $options['login']          = $this->getHttpLogin();
        $options['password']       = $this->getHttpPassword();
        $options['proxy_host']     = $this->getProxyHost();
        $options['proxy_port']     = $this->getProxyPort();
        $options['proxy_login']    = $this->getProxyLogin();
        $options['proxy_password'] = $this->getProxyPassword();
        $options['local_cert']     = $this->getHttpsCertificate();
        $options['passphrase']     = $this->getHttpsCertPassphrase();
        $options['compression']    = $this->getCompressionOptions();
        $options['connection_timeout'] = $this->_connection_timeout;
        $options['stream_context'] = $this->getStreamContext();
        $options['cache_wsdl']     = $this->getWsdlCache();
        $options['features']       = $this->getSoapFeatures();
        $options['user_agent']     = $this->getUserAgent();
        
        foreach ($options as $key => $value) {
            /*
             * ugly hack as I don't know if checking for '=== null'
            * breaks some other option
            */
            if (in_array($key, array('user_agent', 'cache_wsdl', 'compression'))) {
                if ($value === null) {
                    unset($options[$key]);
                }
            } else {
                if ($value == null) {
                    unset($options[$key]);
                }
            }
        }
    
        return $options;
    }
    
    
    
    /**
     * Initialize SOAP Client object
     *
     * @throws Zend_Soap_Client_Exception
     */
    protected function _initSoapClientObject()
    {
        $wsdl = $this->getWsdl();
        $options = array_merge($this->getOptions(), array('trace' => false, 'exceptions' => 1));
    
        if ($wsdl == null) {
            if (!isset($options['location'])) {
                require_once 'Zend/Soap/Client/Exception.php';
                throw new Zend_Soap_Client_Exception('\'location\' parameter is required in non-WSDL mode.');
            }
            if (!isset($options['uri'])) {
                require_once 'Zend/Soap/Client/Exception.php';
                throw new Zend_Soap_Client_Exception('\'uri\' parameter is required in non-WSDL mode.');
            }
        } else {
            if (isset($options['use'])) {
                require_once 'Zend/Soap/Client/Exception.php';
                throw new Zend_Soap_Client_Exception('\'use\' parameter only works in non-WSDL mode.');
            }
            if (isset($options['style'])) {
                require_once 'Zend/Soap/Client/Exception.php';
                throw new Zend_Soap_Client_Exception('\'style\' parameter only works in non-WSDL mode.');
            }
        }
        unset($options['wsdl']);
            
        try {
            $this->_soapClient = new Zend_Soap_Client_Common(array($this, '_doRequest'), $wsdl, $options);
        } catch (SoapFault $e) {  
            throw new Zend_Exception($e->getMessage());
        }
    }
}