2

It seems as though ZendFramework2's various http client adapters allow connect and read timeouts in the seconds range; enforced by casting the various timeout parameters to (int) before setting them. In our organization, we typically specify smaller connection and read timeouts for production environments (we have a SOA in place).

However, the Socket-level functions in php support (float) values for timeouts to support sub-second timeouts, and there are means to support sub-second connect timeouts using libcurl since PHP 5.2.3. (CURLOPT_CONNECTTIMEOUT_MS with cURL complied with c-ares per how enable curl's AsynchDNS?).

I'm happy to enter a ticket to ZendFramework to support smaller, more granular timeouts but wanted to first see if this is a solved problem out here 'in the wild'. Has anyone gotten the Zend\Http\Client\Adapter\Socket or \Curl adapters to support sub-second connect and read timeouts?

Community
  • 1
  • 1
drobert
  • 1,230
  • 8
  • 21

1 Answers1

1

I know two ways to tackle your problem. You can use the as a temporary fix until the Zend Framework 2 supports the sub-second timeout on it's own.

  1. Override the Socket/Curl classes

    Create module and inherit from the Socket / Curl classes and override the connect() method to support sub-second timeout.

    For the Curl class I would add a new option timeout_ms in the configuration array, so you can still use the old timeout option. My code only shows the relevant changes to the timeout section.

    <?php
    use Zend\Http\Client\Adapter\Curl;
    
    class CurlMs extends Curl 
    {
    
      public function connect($host, $port = 80, $secure = false)
      {
        ...
        // Replace the lines with timeout with the following lines.
        if (isset($this->config['timeout_ms'])) {
          // Set timeout milliseconds
          curl_setopt($this->curl, CURLOPT_CONNECTTIMEOUT_MS, $this->config['timeout_ms']);
        } elseif (isset($this->config['timeout'])) {
          // Set timeout
          curl_setopt($this->curl, CURLOPT_CONNECTTIMEOUT, $this->config['timeout']);
        }
        ...
      }
    }
    

    For the Socket class the situation is a little bit harder to solve, because of the different interfaces of stream_socket_client() and stream_socket_timeout(). The option timeout is now a float value. As above I only included the relevant code parts.

    <?php
    use Zend\Http\Client\Adapter\Socket;
    
    class SocketMs extends Socket 
    {
    
      public function connectconnect($host, $port = 80, $secure = false)
      { 
       ...
        // Replace the lines with socket connect
        $this->socket = stream_socket_client(
          $host . ':' . $port,
          $errno,
          $errstr,
          (float) $this->config['timeout'],
          $flags,
          $context
        );
        ...
        // Replace the lines with stream timeout
        // get the fraction of the float value and convert it microseconds
        $fraction = ($this->config['timeout'] - floor($this->config['timeout'])) * 1000;
        // Set the stream timeout               
        if (!stream_set_timeout($this->socket, (int) $this->config['timeout'], (int) $fraction)  {                                      
          throw new AdapterException\RuntimeException('Unable to set the connection timeout');
        }
        ...
      }
    }
    
  2. Patch the Socket.php / Curl.php files

    Write a patch for the files you where you want to add the sub-second timeout and apply them to your ZendFramework 2 version. You can automate the process with composer if you use it. I created the patches against the ZendFramework 2 version 2.3.3.

    I wouldn't recommend this approach, because it has some pitfalls.

    The patch for the Curl.php file would like the following:

    --- a/library/Zend/Http/Client/Adapter/Curl.php
    +++ b/library/Zend/Http/Client/Adapter/Curl.php
    @@ -200,7 +200,10 @@ class Curl implements HttpAdapter, StreamInterface
                 curl_setopt($this->curl, CURLOPT_PORT, intval($port));
             }
    
    -        if (isset($this->config['timeout'])) {
    +        if (isset($this->config['timeout_ms'])) {
    +            // Set timeout milliseconds
    +            curl_setopt($this->curl, CURLOPT_CONNECTTIMEOUT_MS, $this->config['timeout_ms']);
    +        } elseif (isset($this->config['timeout'])) {
                 // Set timeout
                 curl_setopt($this->curl, CURLOPT_CONNECTTIMEOUT, $this->config['timeout']);
             }
    

    The patch for the Socket.php file would be like this:

    --- a/library/Zend/Http/Client/Adapter/Socket.php
    +++ b/library/Zend/Http/Client/Adapter/Socket.php
    @@ -247,7 +247,7 @@ class Socket implements HttpAdapter, StreamInterface
                     $host . ':' . $port,
                     $errno,
                     $errstr,
    -                (int) $this->config['timeout'],
    +                (float) $this->config['timeout'],
                     $flags,
                     $context
                 );
    @@ -268,7 +268,9 @@ class Socket implements HttpAdapter, StreamInterface
                 }
    
                 // Set the stream timeout
    -            if (!stream_set_timeout($this->socket, (int) $this->config['timeout'])) {
    +            // get the fraction of the timeout and convert it to microseconds
    +            $fraction = ($this->config['timeout'] - floor($this->config['timeout'])) * 1000;
    +            if (!stream_set_timeout($this->socket, (int) $this->config['timeout'], (int) $fraction)) {
                     throw new AdapterException\RuntimeException('Unable to set the connection timeout');
                 }
    
ByteNudger
  • 1,545
  • 5
  • 29
  • 37