Skip to content
This repository has been archived by the owner on Jan 30, 2020. It is now read-only.

Commit

Permalink
Merge branch 'httpResponseBug' of https://github.com/dgasparri/zf2-te…
Browse files Browse the repository at this point in the history
…stserver into feature/http-response-stream
  • Loading branch information
weierophinney committed May 30, 2012
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 45 deletions.
172 changes: 127 additions & 45 deletions src/Response/Stream.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@

namespace Zend\Http\Response;

use Zend\Http\Response;
use Zend\Http\Response,
Zend\Http\Exception;


/**
* Zend_Http_Response represents an HTTP 1.0 / 1.1 response message. It
Expand All @@ -35,6 +37,21 @@
*/
class Stream extends Response
{

/**
* The Content-Length value, if set
*
* @var int
*/
protected $contentLength = null;

/**
* The portion of the body that has alredy been streamed
*
* @var int
*/
protected $contentStreamed = 0;

/**
* Response as stream
*
Expand All @@ -49,14 +66,14 @@ class Stream extends Response
*
* @var string
*/
protected $stream_name;
protected $streamName;

/**
* Should we clean up the stream file when this response is closed?
*
* @var boolean
*/
protected $_cleanup;
protected $cleanup;

/**
* Get the response as stream
Expand Down Expand Up @@ -87,7 +104,7 @@ public function setStream($stream)
*/
public function getCleanup()
{
return $this->_cleanup;
return $this->cleanup;
}

/**
Expand All @@ -97,7 +114,7 @@ public function getCleanup()
*/
public function setCleanup($cleanup = true)
{
$this->_cleanup = $cleanup;
$this->cleanup = $cleanup;
}

/**
Expand All @@ -107,40 +124,94 @@ public function setCleanup($cleanup = true)
*/
public function getStreamName()
{
return $this->stream_name;
return $this->streamName;
}

/**
* Set file name associated with the stream
*
* @param string $stream_name Name to set
* @param string $streamName Name to set
* @return Stream
*/
public function setStreamName($stream_name)
public function setStreamName($streamName)
{
$this->stream_name = $stream_name;
$this->streamName = $streamName;
return $this;
}

/**
* Create a new Zend\Http\Response\Stream object from a string

/**
* Create a new Zend\Http\Response\Stream object from a stream
*
* @param string $response_str
* @param resource $stream
* @return Stream
*/
public static function fromStream($response_str, $stream)
{
$response= new static();
* @param string $responseString
* @param resource $stream
* @return Stream
*/
public static function fromStream($responseString, $stream)
{

if (!is_resource($stream)) {
throw new Exception\InvalidArgumentException('A valid stream is required');
}

$response::fromString($response_str);
if (is_resource($stream)) {
$response->setStream($stream);
}
$headerComplete = false;
$headersString = '';

$responseArray = explode("\n",$responseString);

return $response;
while (count($responseArray)) {
$nextLine = array_shift($responseArray);
$headersString .= $nextLine."\n";
$nextLineTrimmed = trim($nextLine);
if ($nextLineTrimmed == "") {
$headerComplete = true;
break;
}

}

if (!$headerComplete) {
while (false !== ($nextLine = fgets($stream))) {

$headersString .= trim($nextLine)."\r\n";
if ($nextLine == "\r\n" || $nextLine == "\n") {
$headerComplete = true;
break;
}
}
}

if (!$headerComplete) {
throw new Exception\OutOfRangeException('End of header not found');
}

$response = static::fromString($headersString);

if (is_resource($stream)) {
$response->setStream($stream);
}

if (count($responseArray)) {
$response->content = implode("\n", $responseArray);
}

$headers = $response->headers();
foreach($headers as $header) {
if ($header instanceof \Zend\Http\Header\ContentLength) {
$response->contentLength = (int) $header->getFieldValue();
if (strlen($response->content) > $response->contentLength) {
throw new Exception\OutOfRangeException(
sprintf('Too much content was extracted from the stream (%d instead of %d bytes)',
strlen($this->content), $this->contentLength));
}
break;
}
}

return $response;
}



/**
* Get the response body as string
*
Expand Down Expand Up @@ -174,36 +245,47 @@ public function getRawBody()
if ($this->stream) {
$this->readStream();
}
return $this->body;
return $this->content;
}


/**
* 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_null($this->contentLength)) {
$bytes = $this->contentLength - $this->contentStreamed;
} else {
$bytes = -1; //Read the whole buffer
}

if (!is_resource($this->stream) || $bytes == 0) {
return '';
}

$this->content .= stream_get_contents($this->stream, $bytes);
$this->contentStreamed += strlen($this->content);

if ($this->contentLength == $this->contentStreamed) {
$this->stream = null;
}
}

/**
* Read stream content and return it as string
*
* Function reads the remainder of the body from the stream and closes the stream.
*
* @return string
* Destructor
*/
protected function readStream()
{
if (!is_resource($this->stream)) {
return '';
}

$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;
$this->stream = null; //Could be listened by others
}
if ($this->_cleanup) {
if ($this->cleanup) {
@unlink($this->stream_name);
}
}

}
104 changes: 104 additions & 0 deletions test/Response/ResponseStreamTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

namespace ZendTest\Http;

use Zend\Http\Response\Stream;

class ResponseStreamTest extends \PHPUnit_Framework_TestCase
{

public function testResponseFactoryFromStringCreatesValidResponse()
{
$string = 'HTTP/1.0 200 OK' . "\r\n\r\n".'Foo Bar'."\r\n";
$stream = fopen('php://temp','rb+');
fwrite($stream, 'Bar Foo');
rewind($stream);

$response = Stream::fromStream($string, $stream);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals("Foo Bar\r\nBar Foo", $response->getBody());
}

public function testGzipResponse ()
{
$stream = fopen(__DIR__ . '/../_files/response_gzip','rb');

$headers = '';
while(false!== ($newLine = fgets($stream))) {
$headers .= $newLine;
if($headers == "\n" || $headers == "\r\n") {
break;
}
}


$headers .= fread($stream, 100); //Should accept also part of body as text

$res = Stream::fromStream($headers, $stream);

$this->assertEquals('gzip', $res->headers()->get('Content-encoding')->getFieldValue());
$this->assertEquals('0b13cb193de9450aa70a6403e2c9902f', md5($res->getBody()));
$this->assertEquals('f24dd075ba2ebfb3bf21270e3fdc5303', md5($res->getContent()));
}


public function test300isRedirect()
{
$values = $this->readResponse('response_302');
$response = Stream::fromStream($values['data'],$values['stream']);

$this->assertEquals(302, $response->getStatusCode(), 'Response code is expected to be 302, but it\'s not.');
$this->assertFalse($response->isClientError(), 'Response is an error, but isClientError() returned true');
$this->assertFalse($response->isForbidden(), 'Response is an error, but isForbidden() returned true');
$this->assertFalse($response->isInformational(), 'Response is an error, but isInformational() returned true');
$this->assertFalse($response->isNotFound(), 'Response is an error, but isNotFound() returned true');
$this->assertFalse($response->isOk(), 'Response is an error, but isOk() returned true');
$this->assertFalse($response->isServerError(), 'Response is an error, but isServerError() returned true');
$this->assertTrue($response->isRedirect(), 'Response is an error, but isRedirect() returned false');
$this->assertFalse($response->isSuccess(), 'Response is an error, but isSuccess() returned true');
}


public function testMultilineHeader()
{
$values = $this->readResponse('response_multiline_header');
$response = Stream::fromStream($values['data'],$values['stream']);

// Make sure we got the corrent no. of headers
$this->assertEquals(6, count($response->headers()), 'Header count is expected to be 6');

// Check header integrity
$this->assertEquals('timeout=15,max=100', $response->headers()->get('keep-alive')->getFieldValue());
$this->assertEquals('text/html;charset=iso-8859-1', $response->headers()->get('content-type')->getFieldValue());
}


/**
* Helper function: read test response from file
*
* @param string $response
* @return string
*/
protected function readResponse($response)
{

$stream = fopen(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . $response, 'rb');

$data = '';
while(false!== ($newLine = fgets($stream))) {
$data .= $newLine;
if($newLine == "\n" || $newLine == "\r\n") {
break;
}
}


$data .= fread($stream, 100); //Should accept also part of body as text

$return = array();
$return['stream'] = $stream;
$return['data'] = $data;

return $return;
}
}

0 comments on commit 85c9491

Please sign in to comment.