Przeglądaj źródła

Fixing ZF-7309: Proper checks for socket read timeouts
- Adding unit tests
- Adding special Exception code READ_TIMEOUT


git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@17026 44c647ce-9c0f-0410-b52a-842ac1e357ba

shahar 16 lat temu
rodzic
commit
6abadca2bc

+ 3 - 1
library/Zend/Http/Client/Adapter/Exception.php

@@ -33,4 +33,6 @@ require_once 'Zend/Http/Client/Exception.php';
  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  */
 class Zend_Http_Client_Adapter_Exception extends Zend_Http_Client_Exception
-{}
+{
+    const READ_TIMEOUT = 1000;
+}

+ 41 - 6
library/Zend/Http/Client/Adapter/Socket.php

@@ -281,6 +281,7 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
         // First, read headers only
         $response = '';
         $gotStatus = false;
+
         while (($line = @fgets($this->socket)) !== false) {
             $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false);
             if ($gotStatus) {
@@ -289,6 +290,8 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
             }
         }
 
+        $this->_checkSocketReadTimeout();
+
         $statusCode = Zend_Http_Response::extractCode($response);
 
         // Handle 100 and 101 responses internally by restarting the read again
@@ -313,9 +316,13 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
         
         // If we got a 'transfer-encoding: chunked' header
         if (isset($headers['transfer-encoding'])) {
+
             if (strtolower($headers['transfer-encoding']) == 'chunked') {
+
                 do {
                     $line  = @fgets($this->socket);
+                    $this->_checkSocketReadTimeout();
+
                     $chunk = $line;
 
                     // Figure out the next chunk size
@@ -334,8 +341,8 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
                     $left_to_read = $chunksize;
                     while ($left_to_read > 0) {
                         $line = @fread($this->socket, $left_to_read);
-                        if ($line === false || strlen($line) === 0)
-                        {
+                        if ($line === false || strlen($line) === 0) {
+                            $this->_checkSocketReadTimeout();
                             break;
                         } else {
                             $chunk .= $line;
@@ -347,6 +354,8 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
                     }
 
                     $chunk .= @fgets($this->socket);
+                    $this->_checkSocketReadTimeout();
+
                     $response .= $chunk;
                 } while ($chunksize > 0);
 
@@ -358,12 +367,14 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
 
         // Else, if we got the content-length header, read this number of bytes
         } elseif (isset($headers['content-length'])) {
+
             $left_to_read = $headers['content-length'];
             $chunk = '';
             while ($left_to_read > 0) {
                 $chunk = @fread($this->socket, $left_to_read);
-                if ($chunk === false || strlen($chunk) === 0)
-                {
+                $this->_checkSocketReadTimeout();
+
+                if ($chunk === false || strlen($chunk) === 0) {
                     break;
                 } else {
                     $left_to_read -= strlen($chunk);
@@ -376,14 +387,16 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
 
         // Fallback: just read the response until EOF
         } else {
+
             do {
                 $buff = @fread($this->socket, 8192);
-                if ($buff === false || strlen($buff) === 0)
-                {
+                if ($buff === false || strlen($buff) === 0) {
+                    $this->_checkSocketReadTimeout();
                     break;
                 } else {
                     $response .= $buff;
                 }
+
             } while (feof($this->socket) === false);
 
             $this->close();
@@ -409,6 +422,28 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
     }
 
     /**
+     * Check if the socket has timed out - if so close connection and throw 
+     * an exception
+     *
+     * @throws Zend_Http_Client_Adapter_Exception with READ_TIMEOUT code
+     */
+    protected function _checkSocketReadTimeout()
+    {
+        if ($this->socket) {
+            $info = stream_get_meta_data($this->socket);
+            $timedout = $info['timed_out'];
+            if ($timedout) {
+                $this->close();
+                require_once 'Zend/Http/Client/Adapter/Exception.php';
+                throw new Zend_Http_Client_Adapter_Exception(
+                    "Read timed out after {$this->config['timeout']} seconds",
+                    Zend_Http_Client_Adapter_Exception::READ_TIMEOUT
+                );
+            }
+        }
+    }
+
+    /**
      * Destructor: make sure the socket is disconnected
      *
      * If we are in persistent TCP mode, will not close the connection

+ 26 - 0
tests/Zend/Http/Client/SocketTest.php

@@ -110,6 +110,32 @@ class Zend_Http_Client_SocketTest extends Zend_Http_Client_CommonHttpTests
     }
 
     /**
+     * Test that we get the right exception after a socket timeout
+     * 
+     * @link http://framework.zend.com/issues/browse/ZF-7309
+     */
+    public function testExceptionOnReadTimeout()
+    {
+        // Set 1 second timeout
+        $this->client->setConfig(array('timeout' => 1));
+        
+        $start = microtime(true);
+        
+        try {
+            $this->client->request();
+            $this->fail("Expected a timeout Zend_Http_Client_Adapter_Exception");
+        } catch (Zend_Http_Client_Adapter_Exception $e) {
+            $this->assertEquals(Zend_Http_Client_Adapter_Exception::READ_TIMEOUT, $e->getCode());
+        }
+        
+        $time = (microtime(true) - $start);
+        
+        // We should be very close to 1 second
+        $this->assertLessThan(2, $time);
+    }
+
+    
+    /**
      * Data Providers
      */
 

+ 8 - 0
tests/Zend/Http/Client/_files/testExceptionOnReadTimeout.php

@@ -0,0 +1,8 @@
+<?php
+
+/**
+ * This script does nothing but sleep, and is used to test how 
+ * Zend_Http_Client handles an exceeded timeout
+ */
+
+sleep(5);