Pārlūkot izejas kodu

Allow using streams while sending and receiving data (ZF-6736)

git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@19217 44c647ce-9c0f-0410-b52a-842ac1e357ba
stas 16 gadi atpakaļ
vecāks
revīzija
d44f8812a7

+ 103 - 10
library/Zend/Http/Client.php

@@ -45,6 +45,11 @@ require_once 'Zend/Http/Client/Adapter/Interface.php';
 require_once 'Zend/Http/Response.php';
 
 /**
+ * @see Zend_Http_Response_Stream
+ */
+require_once 'Zend/Http/Response/Stream.php';
+
+/**
  * Zend_Http_Client is an implemetation of an HTTP client in PHP. The client
  * supports basic features like sending different HTTP requests and handling
  * redirections, as well as more advanced features like proxy settings, HTTP
@@ -110,7 +115,8 @@ class Zend_Http_Client
         'httpversion'     => self::HTTP_1,
         'keepalive'       => false,
         'storeresponse'   => true,
-        'strict'          => true
+        'strict'          => true,
+        'output_stream'	  => false,
     );
 
     /**
@@ -733,8 +739,10 @@ class Zend_Http_Client
      * 1. For advanced user who would like to set their own data, already encoded
      * 2. For backwards compatibilty: If someone uses the old post($data) method.
      *    this method will be used to set the encoded data.
+     * 
+     * $data can also be stream (such as file) from which the data will be read.
      *
-     * @param string $data
+     * @param string|resource $data
      * @param string $enctype
      * @return Zend_Http_Client
      */
@@ -742,7 +750,13 @@ class Zend_Http_Client
     {
         $this->raw_post_data = $data;
         $this->setEncType($enctype);
-
+        if (is_resource($data)) {
+            // We've got stream data
+            $stat = @fstat($data);
+            if($stat) {
+                $this->setHeaders(self::CONTENT_LENGTH, $stat['size']);
+            }
+        }
         return $this;
     }
 
@@ -846,6 +860,51 @@ class Zend_Http_Client
     }
 
     /**
+     * Set streaming for received data
+     * 
+     * @param string|boolean $streamfile Stream file, true for temp file, false/null for no streaming
+     * @return Zend_Http_Client
+     */
+    public function setStream($streamfile = true)
+    {
+        $this->setConfig(array("output_stream" => $streamfile));
+        return $this;
+    }
+    
+    /**
+     * Get status of streaming for received data
+     * @return boolean|string
+     */
+    public function getStream()
+    {
+        return $this->config["output_stream"];
+    }
+    
+    /**
+     * Create temporary stream
+     * 
+     * @return resource
+     */
+    protected function _openTempStream()
+    {
+        $this->_stream_name = $this->config['output_stream'];
+        if(!is_string($this->_stream_name)) {
+            // If name is not given, create temp name
+            $this->_stream_name = tempnam(isset($this->config['stream_tmp_dir'])?$this->config['stream_tmp_dir']:sys_get_temp_dir(),
+                 'Zend_Http_Client');
+        }
+        
+        $fp = fopen($this->_stream_name, "w+b");
+        if(!$fp) {
+                $this->close();
+                require_once 'Zend/Http/Client/Exception.php';
+                throw new Zend_Http_Client_Exception("Could not open temp file $name");
+            
+        }
+        return $fp;
+    }
+
+    /**
      * Send the HTTP request and return an HTTP response object
      *
      * @param string $method
@@ -888,21 +947,52 @@ class Zend_Http_Client
             $body = $this->_prepareBody();
             $headers = $this->_prepareHeaders();
 
+            // check that adapter supports streaming before using it
+            if(is_resource($body) && !($this->adapter instanceof Zend_Http_Client_Adapter_Stream)) {
+                /** @see Zend_Http_Client_Exception */
+                require_once 'Zend/Http/Client/Exception.php';
+                throw new Zend_Http_Client_Exception('Adapter does not support streaming');
+            }
+            
             // Open the connection, send the request and read the response
             $this->adapter->connect($uri->getHost(), $uri->getPort(),
                 ($uri->getScheme() == 'https' ? true : false));
 
+            if($this->config['output_stream']) {
+                if($this->adapter instanceof Zend_Http_Client_Adapter_Stream) {
+                    $stream = $this->_openTempStream();
+                    $this->adapter->setOutputStream($stream);
+                } else {
+                	/** @see Zend_Http_Client_Exception */
+                    require_once 'Zend/Http/Client/Exception.php';
+                    throw new Zend_Http_Client_Exception('Adapter does not support streaming');
+                }
+            } 
+                
             $this->last_request = $this->adapter->write($this->method,
                 $uri, $this->config['httpversion'], $headers, $body);
 
             $response = $this->adapter->read();
-            if (! $response) {
-                /** @see Zend_Http_Client_Exception */
-                require_once 'Zend/Http/Client/Exception.php';
-                throw new Zend_Http_Client_Exception('Unable to read response, or response is empty');
+	        if (! $response) {
+	            /** @see Zend_Http_Client_Exception */
+	            require_once 'Zend/Http/Client/Exception.php';
+	            throw new Zend_Http_Client_Exception('Unable to read response, or response is empty');
+	        }
+	
+	        if($this->config['output_stream']) {
+	            rewind($stream);
+	            // cleanup the adapter
+	            $this->adapter->setOutputStream(null);
+	            $response = Zend_Http_Response_Stream::fromStream($response, $stream);
+	            $response->setStreamName($this->_stream_name);
+	            if(!is_string($this->config['output_stream'])) {
+	                // we used temp name, will need to clean up
+	                $response->setCleanup(true);
+	            }
+	        } else {
+	            $response = Zend_Http_Response::fromString($response);
             }
-
-            $response = Zend_Http_Response::fromString($response);
+            
             if ($this->config['storeresponse']) {
                 $this->last_response = $response;
             }
@@ -1056,7 +1146,10 @@ class Zend_Http_Client
         if ($this->method == self::TRACE) {
             return '';
         }
-
+        
+        if (isset($this->raw_post_data) && is_resource($this->raw_post_data)) {
+            return $this->raw_post_data;
+        }
         // If mbstring overloads substr and strlen functions, we have to
         // override it's internal encoding
         if (function_exists('mb_internal_encoding') &&

+ 78 - 25
library/Zend/Http/Client/Adapter/Curl.php

@@ -30,6 +30,10 @@ require_once 'Zend/Uri/Http.php';
  * @see Zend_Http_Client_Adapter_Interface
  */
 require_once 'Zend/Http/Client/Adapter/Interface.php';
+/**
+ * @see Zend_Http_Client_Adapter_Stream
+ */
+require_once 'Zend/Http/Client/Adapter/Stream.php';
 
 /**
  * An adapter class for Zend_Http_Client based on the curl extension.
@@ -41,7 +45,7 @@ require_once 'Zend/Http/Client/Adapter/Interface.php';
  * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  */
-class Zend_Http_Client_Adapter_Curl implements Zend_Http_Client_Adapter_Interface
+class Zend_Http_Client_Adapter_Curl implements Zend_Http_Client_Adapter_Interface, Zend_Http_Client_Adapter_Stream
 {
     /**
      * Parameters array
@@ -95,6 +99,13 @@ class Zend_Http_Client_Adapter_Curl implements Zend_Http_Client_Adapter_Interfac
     protected $_response = null;
 
     /**
+     * Stream for storing output
+     * 
+     * @var resource
+     */
+    protected $out_stream;
+    
+    /**
      * Adapter constructor
      *
      * Config is set using setConfig()
@@ -151,17 +162,6 @@ class Zend_Http_Client_Adapter_Curl implements Zend_Http_Client_Adapter_Interfac
 
         return $this;
     }
-    
-    /**
-     * Retrieve the array of all configuration options which
-     * are not simply passed immediately to CURL extension.
-     *
-     * @return array
-     */
-    public function getConfig()
-    {
-        return $this->_config;
-    }
 
     /**
      * Direct setter for cURL adapter related options.
@@ -278,20 +278,30 @@ class Zend_Http_Client_Adapter_Curl implements Zend_Http_Client_Adapter_Interfac
             case Zend_Http_Client::PUT:
                 // There are two different types of PUT request, either a Raw Data string has been set
                 // or CURLOPT_INFILE and CURLOPT_INFILESIZE are used.
+                if(is_resource($body)) {
+                    $this->_config['curloptions'][CURLOPT_INFILE] = $body;
+                }
                 if (isset($this->_config['curloptions'][CURLOPT_INFILE])) {
-                    if (!isset($this->_config['curloptions'][CURLOPT_INFILESIZE])) {
-                        require_once 'Zend/Http/Client/Adapter/Exception.php';
-                        throw new Zend_Http_Client_Adapter_Exception("Cannot set a file-handle for cURL option CURLOPT_INFILE without also setting its size in CURLOPT_INFILESIZE.");
-                    }
-
                     // Now we will probably already have Content-Length set, so that we have to delete it
                     // from $headers at this point:
                     foreach ($headers AS $k => $header) {
-                        if (stristr($header, "Content-Length:") !== false) {
+                        if (preg_match('/Content-Length:\s*(\d+)/i', $header, $m)) {
+                            if(is_resource($body)) {
+                                $this->_config['curloptions'][CURLOPT_INFILESIZE] = (int)$m[1];
+                            }
                             unset($headers[$k]);
                         }
                     }
-
+                    
+                    if (!isset($this->_config['curloptions'][CURLOPT_INFILESIZE])) {
+                        require_once 'Zend/Http/Client/Adapter/Exception.php';
+                        throw new Zend_Http_Client_Adapter_Exception("Cannot set a file-handle for cURL option CURLOPT_INFILE without also setting its size in CURLOPT_INFILESIZE.");
+                    }
+                    
+                    if(is_resource($body)) {
+                        $body = '';
+                    }
+                    
                     $curlMethod = CURLOPT_PUT;
                 } else {
                     $curlMethod = CURLOPT_CUSTOMREQUEST;
@@ -320,6 +330,11 @@ class Zend_Http_Client_Adapter_Curl implements Zend_Http_Client_Adapter_Interfac
                 throw new Zend_Http_Client_Adapter_Exception("Method currently not supported");
         }
 
+        if(is_resource($body) && $curlMethod != CURLOPT_PUT) {
+            require_once 'Zend/Http/Client/Adapter/Exception.php';
+            throw new Zend_Http_Client_Adapter_Exception("Streaming requests are allowed only with PUT");
+        }
+        
         // get http version to use
         $curlHttp = ($http_ver = 1.1) ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_1_0;
 
@@ -327,11 +342,19 @@ class Zend_Http_Client_Adapter_Curl implements Zend_Http_Client_Adapter_Interfac
         curl_setopt($this->_curl, $curlHttp, true);
         curl_setopt($this->_curl, $curlMethod, $curlValue);
 
-        // ensure headers are also returned
-        curl_setopt($this->_curl, CURLOPT_HEADER, true);
-
-        // ensure actual response is returned
-        curl_setopt($this->_curl, CURLOPT_RETURNTRANSFER, true);
+        if($this->out_stream) {
+            // headers will be read into the response
+            curl_setopt($this->_curl, CURLOPT_HEADER, false);
+            curl_setopt($this->_curl, CURLOPT_HEADERFUNCTION, array($this, "readHeader"));
+            // and data will be written into the file
+            curl_setopt($this->_curl, CURLOPT_FILE, $this->out_stream);     
+        } else {
+            // ensure headers are also returned
+            curl_setopt($this->_curl, CURLOPT_HEADER, true);
+
+            // ensure actual response is returned
+            curl_setopt($this->_curl, CURLOPT_RETURNTRANSFER, true);
+        }
 
         // set additional headers
         $headers['Accept'] = '';
@@ -369,7 +392,12 @@ class Zend_Http_Client_Adapter_Curl implements Zend_Http_Client_Adapter_Interfac
         }
 
         // send the request
-        $this->_response = curl_exec($this->_curl);
+        $response = curl_exec($this->_curl);
+        
+        // if we used streaming, headers are already there
+        if(!is_resource($this->out_stream)) {
+            $this->_response = $response;
+        }
 
         $request  = curl_getinfo($this->_curl, CURLINFO_HEADER_OUT);
         $request .= $body;
@@ -435,4 +463,29 @@ class Zend_Http_Client_Adapter_Curl implements Zend_Http_Client_Adapter_Interfac
     {
         return $this->_curl;
     }
+
+    /**
+     * Set output stream for the response
+     * 
+     * @param resource $stream
+     * @return Zend_Http_Client_Adapter_Socket
+     */
+    public function setOutputStream($stream) 
+    {
+        $this->out_stream = $stream;
+        return $this;
+    }
+    
+    /**
+     * Header reader function for CURL
+     * 
+     * @param resource $curl
+     * @param string $header
+     * @return int
+     */
+    public function readHeader($curl, $header)
+    {
+        $this->_response .= $header;
+        return strlen($header);
+    }
 }

+ 88 - 38
library/Zend/Http/Client/Adapter/Socket.php

@@ -29,6 +29,10 @@ require_once 'Zend/Uri/Http.php';
  * @see Zend_Http_Client_Adapter_Interface
  */
 require_once 'Zend/Http/Client/Adapter/Interface.php';
+/**
+ * @see Zend_Http_Client_Adapter_Stream
+ */
+require_once 'Zend/Http/Client/Adapter/Stream.php';
 
 /**
  * A sockets based (stream_socket_client) adapter class for Zend_Http_Client. Can be used
@@ -40,7 +44,7 @@ require_once 'Zend/Http/Client/Adapter/Interface.php';
  * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  */
-class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interface
+class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interface, Zend_Http_Client_Adapter_Stream
 {
     /**
      * The socket for server connection
@@ -57,6 +61,13 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
     protected $connected_to = array(null, null);
 
     /**
+     * Stream for storing output
+     * 
+     * @var resource
+     */
+    protected $out_stream = null;
+    
+    /**
      * Parameters array
      *
      * @var array
@@ -111,16 +122,6 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
             $this->config[strtolower($k)] = $v;
         }
     }
-    
-    /**
-     * Retrieve the array of all configuration options
-     *
-     * @return array
-     */
-    public function getConfig()
-    {
-        return $this->config;
-    }
 
     /**
      * Set the stream context for the TCP connection to the server
@@ -272,14 +273,25 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
             $request .= "$v\r\n";
         }
 
-        // Add the request body
-        $request .= "\r\n" . $body;
-
+        if(is_resource($body)) {
+            $request .= "\r\n";
+        } else {
+            // Add the request body
+            $request .= "\r\n" . $body;
+        }
+        
         // Send the request
         if (! @fwrite($this->socket, $request)) {
             require_once 'Zend/Http/Client/Adapter/Exception.php';
             throw new Zend_Http_Client_Adapter_Exception('Error writing request to server');
         }
+        
+        if(is_resource($body)) {
+            if(stream_copy_to_stream($body, $this->socket) == 0) {
+                require_once 'Zend/Http/Client/Adapter/Exception.php';
+                throw new Zend_Http_Client_Adapter_Exception('Error writing request to server');
+            }
+        }
 
         return $request;
     }
@@ -294,6 +306,7 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
         // First, read headers only
         $response = '';
         $gotStatus = false;
+        $stream = !empty($this->config['stream']);
 
         while (($line = @fgets($this->socket)) !== false) {
             $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false);
@@ -302,7 +315,7 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
                 if (rtrim($line) === '') break;
             }
         }
-
+        
         $this->_checkSocketReadTimeout();
 
         $statusCode = Zend_Http_Response::extractCode($response);
@@ -329,7 +342,7 @@ 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 {
@@ -357,28 +370,39 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
                         $current_pos = ftell($this->socket);
                         if ($current_pos >= $read_to) break;
 
-                        $line = @fread($this->socket, $read_to - $current_pos);
-                        if ($line === false || strlen($line) === 0) {
-                            $this->_checkSocketReadTimeout();
-                            break;
+                        if($this->out_stream) {
+                            if(stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) {
+                              $this->_checkSocketReadTimeout();
+                              break;   
+                             }
                         } else {
-                            $chunk .= $line;
+                            $line = @fread($this->socket, $read_to - $current_pos);
+                            if ($line === false || strlen($line) === 0) {
+                                $this->_checkSocketReadTimeout();
+                                break;
+                            }
+                                    $chunk .= $line;
                         }
-
                     } while (! feof($this->socket));
 
                     $chunk .= @fgets($this->socket);
                     $this->_checkSocketReadTimeout();
 
-                    $response .= $chunk;
+                    if(!$this->out_stream) {
+                        $response .= $chunk;
+                    }
                 } while ($chunksize > 0);
-
             } else {
                 $this->close();
                 throw new Zend_Http_Client_Adapter_Exception('Cannot handle "' .
                     $headers['transfer-encoding'] . '" transfer encoding');
             }
-
+            
+            // We automatically decode chunked-messages when writing to a stream
+            // this means we have to disallow the Zend_Http_Response to do it again
+            if ($this->out_stream) {
+                $response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $response);
+            }
         // Else, if we got the content-length header, read this number of bytes
         } elseif (isset($headers['content-length'])) {
 
@@ -389,13 +413,20 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
                  $read_to > $current_pos;
                  $current_pos = ftell($this->socket)) {
 
-                $chunk = @fread($this->socket, $read_to - $current_pos);
-                if ($chunk === false || strlen($chunk) === 0) {
-                    $this->_checkSocketReadTimeout();
-                    break;
-                }
+                 if($this->out_stream) {
+                     if(@stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) {
+                          $this->_checkSocketReadTimeout();
+                          break;   
+                     }
+                 } else {
+                    $chunk = @fread($this->socket, $read_to - $current_pos);
+                    if ($chunk === false || strlen($chunk) === 0) {
+                        $this->_checkSocketReadTimeout();
+                        break;
+                    }
 
-                $response .= $chunk;
+                    $response .= $chunk;
+                }
 
                 // Break if the connection ended prematurely
                 if (feof($this->socket)) break;
@@ -405,12 +436,19 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
         } else {
 
             do {
-                $buff = @fread($this->socket, 8192);
-                if ($buff === false || strlen($buff) === 0) {
-                    $this->_checkSocketReadTimeout();
-                    break;
-                } else {
-                    $response .= $buff;
+                if($this->out_stream) {
+                    if(@stream_copy_to_stream($this->socket, $this->out_stream) == 0) {
+                          $this->_checkSocketReadTimeout();
+                          break;   
+                     }
+                }  else {
+                    $buff = @fread($this->socket, 8192);
+                    if ($buff === false || strlen($buff) === 0) {
+                        $this->_checkSocketReadTimeout();
+                        break;
+                    } else {
+                        $response .= $buff;
+                    }
                 }
 
             } while (feof($this->socket) === false);
@@ -458,7 +496,19 @@ class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interf
             }
         }
     }
-
+    
+    /**
+     * Set output stream for the response
+     * 
+     * @param resource $stream
+     * @return Zend_Http_Client_Adapter_Socket
+     */
+    public function setOutputStream($stream) 
+    {
+        $this->out_stream = $stream;
+        return $this;
+    }
+    
     /**
      * Destructor: make sure the socket is disconnected
      *

+ 46 - 0
library/Zend/Http/Client/Adapter/Stream.php

@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage Client_Adapter
+ * @version    $Id: Interface.php 16214 2009-06-21 19:34:03Z thomas $
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * An interface description for Zend_Http_Client_Adapter_Stream classes.
+ *
+ * This interface decribes Zend_Http_Client_Adapter which supports streaming.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage Client_Adapter
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+interface Zend_Http_Client_Adapter_Stream
+{
+    /**
+     * Set output stream
+     * 
+     * This function sets output stream where the result will be stored.
+     * 
+     * @param resource $stream Stream to write the output to
+     * 
+     */
+    function setOutputStream($stream);
+}

+ 236 - 0
library/Zend/Http/Response/Stream.php

@@ -0,0 +1,236 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage Response
+ * @version    $Id: Response.php 17131 2009-07-26 10:03:39Z shahar $
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Zend_Http_Response represents an HTTP 1.0 / 1.1 response message. It
+ * includes easy access to all the response's different elemts, as well as some
+ * convenience methods for parsing and validating HTTP responses.
+ *
+ * @package    Zend_Http
+ * @subpackage Response
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_Response_Stream extends Zend_Http_Response
+{
+    /**
+     * Response as stream
+     *
+     * @var resource
+     */
+    protected $stream;
+    
+    /**
+     * The name of the file containing the stream
+     * 
+     * Will be empty if stream is not file-based.
+     * 
+     * @var string
+     */
+    protected $stream_name;
+
+    /* *
+     * Should we clean up the stream file when this response is closed?
+     * 
+     * @var boolean
+     */
+    protected $_cleanup;
+    
+    /**
+     * Get the response as stream
+     *
+     * @return resourse
+     */
+    public function getStream()
+    {
+        return $this->stream;
+    }
+    
+    /**
+     * Set the response stream
+     *
+     * @param resourse $stream
+     * @return Zend_Http_Response_Stream
+     */
+    public function setStream($stream)
+    {
+        $this->stream = $stream;
+        return $this;
+    }
+
+	/**
+	 * Get the cleanup trigger
+	 * 
+	 * @return boolean
+	 */
+	public function getCleanup() {
+		return $this->_cleanup;
+	}
+
+	/**
+	 * Set the cleanup trigger
+	 * 
+	 * @param $cleanup Set cleanup trigger
+	 */
+	public function setCleanup($cleanup = true) {
+		$this->_cleanup = $cleanup;
+	}
+
+    /**
+     * Get file name associated with the stream
+     * 
+	 * @return string
+	 */
+	public function getStreamName() {
+		return $this->stream_name;
+	}
+
+	/**
+	 * Set file name associated with the stream
+	 * 
+	 * @param string $stream_name Name to set
+	 * @return Zend_Http_Response_Stream
+	 */
+	public function setStreamName($stream_name) {
+		$this->stream_name = $stream_name;
+		return $this;
+	}
+    
+    
+    /**
+     * HTTP response constructor
+     *
+     * In most cases, you would use Zend_Http_Response::fromString to parse an HTTP
+     * response string and create a new Zend_Http_Response object.
+     *
+     * NOTE: The constructor no longer accepts nulls or empty values for the code and
+     * headers and will throw an exception if the passed values do not form a valid HTTP
+     * responses.
+     *
+     * If no message is passed, the message will be guessed according to the response code.
+     *
+     * @param int $code Response code (200, 404, ...)
+     * @param array $headers Headers array
+     * @param string $body Response body
+     * @param string $version HTTP version
+     * @param string $message Response code as text
+     * @throws Zend_Http_Exception
+     */
+
+	public function __construct($code, $headers, $body = null, $version = '1.1', $message = null)
+    {
+        
+        if(is_resource($body)) {
+            $this->setStream($body);
+            $body = '';
+        }
+        parent::__construct($code, $headers, $body, $version, $message);
+    }
+    
+    /**
+     * Create a new Zend_Http_Response_Stream object from a string
+     *
+     * @param string $response_str
+     * @param resource $stream
+     * @return Zend_Http_Response_Stream
+     */
+    public static function fromStream($response_str, $stream)
+    {
+        $code    = self::extractCode($response_str);
+        $headers = self::extractHeaders($response_str);
+        $version = self::extractVersion($response_str);
+        $message = self::extractMessage($response_str);
+
+        return new self($code, $headers, $stream, $version, $message);
+    }
+    
+    /**
+     * Get the response body as string
+     *
+     * This method returns the body of the HTTP response (the content), as it
+     * should be in it's readable version - that is, after decoding it (if it
+     * was decoded), deflating it (if it was gzip compressed), etc.
+     *
+     * If you want to get the raw body (as transfered on wire) use
+     * $this->getRawBody() instead.
+     *
+     * @return string
+     */
+    public function getBody()
+    {
+        if($this->stream != null) {
+            $this->readStream();
+        }
+        return parent::getBody();
+    }
+    
+    /**
+     * Get the raw response body (as transfered "on wire") as string
+     *
+     * If the body is encoded (with Transfer-Encoding, not content-encoding -
+     * IE "chunked" body), gzip compressed, etc. it will not be decoded.
+     *
+     * @return string
+     */
+    public function getRawBody()
+    {
+        if($this->stream) {
+            $this->readStream();
+        }
+        return $this->body;
+    }
+    
+    /**
+     * Read stream content and return it as string
+     * 
+     * Function reads the remainder of the body from the stream and closes the stream.
+     * 
+     * @return string
+     */
+    protected function readStream()
+    {
+        if(!is_resource($this->stream)) {
+            return '';
+        }
+
+        if(isset($headers['content-length'])) {
+            $this->body = stream_get_contents($this->stream, $headers['content-length']);
+        } else {
+            $this->body = stream_get_contents($this->stream);
+        }
+        fclose($this->stream);
+        $this->stream = null;
+    }
+ 
+    public function __destruct()
+    {
+        if(is_resource($this->stream)) {
+            fclose($this->stream);
+            $this->stream = null;
+        }
+        if($this->_cleanup) {
+            @unlink($this->stream_name);
+        }
+    }
+    
+}