Skip to content

Commit 693c3c8

Browse files
committed
Send empty response body for closed response body stream
1 parent baa0701 commit 693c3c8

File tree

4 files changed

+107
-7
lines changed

4 files changed

+107
-7
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,9 @@ This is just a example you could use of the streaming,
507507
you could also send a big amount of data via little chunks
508508
or use it for body data that needs to calculated.
509509

510+
If the request handler resolves with a response stream that is already closed,
511+
it will simply send an empty response body.
512+
510513
If the response body is a `string`, a `Content-Length` header will be added
511514
automatically.
512515
If the response body is a ReactPHP `ReadableStreamInterface` and you do not

src/Server.php

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -339,15 +339,22 @@ private function handleResponseBody(ResponseInterface $response, ConnectionInter
339339
return $connection->end();
340340
}
341341

342-
$body = $response->getBody();
343-
$stream = $body;
344-
345-
if ($response->getHeaderLine('Transfer-Encoding') === 'chunked') {
346-
$stream = new ChunkedEncoder($body);
347-
}
342+
$stream = $response->getBody();
348343

349344
$connection->write(Psr7Implementation\str($response));
350-
$stream->pipe($connection);
345+
if ($stream->isReadable()) {
346+
if ($response->getHeaderLine('Transfer-Encoding') === 'chunked') {
347+
$stream = new ChunkedEncoder($stream);
348+
}
349+
350+
$stream->pipe($connection);
351+
} else {
352+
if ($response->getHeaderLine('Transfer-Encoding') === 'chunked') {
353+
$connection->write("0\r\n\r\n");
354+
}
355+
356+
$connection->end();
357+
}
351358
}
352359

353360
/**

tests/FunctionalServerTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use React\Promise\Promise;
1717
use React\Promise\PromiseInterface;
1818
use React\Promise\Stream;
19+
use React\Stream\ThroughStream;
1920

2021
class FunctionalServerTest extends TestCase
2122
{
@@ -351,6 +352,33 @@ public function testSecureHttpsOnHttpStandardPortReturnsUriWithPort()
351352

352353
$socket->close();
353354
}
355+
356+
public function testClosedStreamFromRequestHandlerWillBeSendEmptyBody()
357+
{
358+
$loop = Factory::create();
359+
$socket = new Socket(0, $loop);
360+
$connector = new Connector($loop);
361+
362+
$stream = new ThroughStream();
363+
$stream->close();
364+
365+
$server = new Server($socket, function (RequestInterface $request) use ($stream) {
366+
return new Response(200, array(), $stream);
367+
});
368+
369+
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) use ($loop) {
370+
$conn->write("GET / HTTP/1.0\r\n\r\n");
371+
372+
return Stream\buffer($conn);
373+
});
374+
375+
$response = Block\await($result, $loop, 1.0);
376+
377+
$this->assertStringStartsWith("HTTP/1.0 200 OK", $response);
378+
$this->assertStringEndsWith("\r\n\r\n", $response);
379+
380+
$socket->close();
381+
}
354382
}
355383

356384
function noScheme($uri)

tests/ServerTest.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,68 @@ function ($data) use (&$buffer) {
646646
$this->assertEquals('', $buffer);
647647
}
648648

649+
public function testStreamAlreadyClosedWillSendEmptyBodyChunkedEncoded()
650+
{
651+
$stream = new ThroughStream();
652+
$stream->close();
653+
654+
$server = new Server($this->socket, function (ServerRequestInterface $request) use ($stream) {
655+
return new Response(200, array(), $stream);
656+
});
657+
658+
$buffer = '';
659+
660+
$this->connection
661+
->expects($this->any())
662+
->method('write')
663+
->will(
664+
$this->returnCallback(
665+
function ($data) use (&$buffer) {
666+
$buffer .= $data;
667+
}
668+
)
669+
);
670+
671+
$this->socket->emit('connection', array($this->connection));
672+
673+
$data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
674+
$this->connection->emit('data', array($data));
675+
676+
$this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $buffer);
677+
$this->assertStringEndsWith("\r\n\r\n0\r\n\r\n", $buffer);
678+
}
679+
680+
public function testStreamAlreadyClosedWillSendEmptyBodyPlainHttp10()
681+
{
682+
$stream = new ThroughStream();
683+
$stream->close();
684+
685+
$server = new Server($this->socket, function (ServerRequestInterface $request) use ($stream) {
686+
return new Response(200, array(), $stream);
687+
});
688+
689+
$buffer = '';
690+
691+
$this->connection
692+
->expects($this->any())
693+
->method('write')
694+
->will(
695+
$this->returnCallback(
696+
function ($data) use (&$buffer) {
697+
$buffer .= $data;
698+
}
699+
)
700+
);
701+
702+
$this->socket->emit('connection', array($this->connection));
703+
704+
$data = "GET / HTTP/1.0\r\nHost: localhost\r\n\r\n";
705+
$this->connection->emit('data', array($data));
706+
707+
$this->assertStringStartsWith("HTTP/1.0 200 OK\r\n", $buffer);
708+
$this->assertStringEndsWith("\r\n\r\n", $buffer);
709+
}
710+
649711
public function testResponseContainsSameRequestProtocolVersionAndChunkedBodyForHttp11()
650712
{
651713
$server = new Server($this->socket, function (ServerRequestInterface $request) {

0 commit comments

Comments
 (0)