Skip to content

Commit e19e987

Browse files
authored
Merge pull request reactphp#430 from clue-labs/no-content-length
Improve assigning `Content-Length` for `304 Not Modified` response
2 parents 3e2caeb + 5f795a0 commit e19e987

File tree

2 files changed

+74
-6
lines changed

2 files changed

+74
-6
lines changed

src/Io/StreamingServer.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -260,23 +260,27 @@ public function handleResponse(ConnectionInterface $connection, ServerRequestInt
260260
$response = $response->withoutHeader('Date');
261261
}
262262

263-
// assign "Content-Length" and "Transfer-Encoding" headers automatically
263+
// assign "Content-Length" header automatically
264264
$chunked = false;
265265
if (($method === 'CONNECT' && $code >= 200 && $code < 300) || ($code >= 100 && $code < 200) || $code === 204) {
266266
// 2xx response to CONNECT and 1xx and 204 MUST NOT include Content-Length or Transfer-Encoding header
267-
$response = $response->withoutHeader('Content-Length')->withoutHeader('Transfer-Encoding');
267+
$response = $response->withoutHeader('Content-Length');
268+
} elseif ($code === 304 && ($response->hasHeader('Content-Length') || $body->getSize() === 0)) {
269+
// 304 Not Modified: preserve explicit Content-Length and preserve missing header if body is empty
268270
} elseif ($body->getSize() !== null) {
269271
// assign Content-Length header when using a "normal" buffered body string
270-
$response = $response->withHeader('Content-Length', (string)$body->getSize())->withoutHeader('Transfer-Encoding');
272+
$response = $response->withHeader('Content-Length', (string)$body->getSize());
271273
} elseif (!$response->hasHeader('Content-Length') && $version === '1.1') {
272274
// assign chunked transfer-encoding if no 'content-length' is given for HTTP/1.1 responses
273-
$response = $response->withHeader('Transfer-Encoding', 'chunked');
274275
$chunked = true;
276+
}
277+
278+
// assign "Transfer-Encoding" header automatically
279+
if ($chunked) {
280+
$response = $response->withHeader('Transfer-Encoding', 'chunked');
275281
} else {
276282
// remove any Transfer-Encoding headers unless automatically enabled above
277-
// we do not want to keep connection alive, so pretend we received "Connection: close" request header
278283
$response = $response->withoutHeader('Transfer-Encoding');
279-
$request = $request->withHeader('Connection', 'close');
280284
}
281285

282286
// assign "Connection" header automatically

tests/Io/StreamingServerTest.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,6 +1362,70 @@ function ($data) use (&$buffer) {
13621362
$this->assertNotContainsString("\r\nContent-Length: 3\r\n", $buffer);
13631363
}
13641364

1365+
public function testResponseContainsNoContentLengthHeaderForNotModifiedStatus()
1366+
{
1367+
$server = new StreamingServer(Factory::create(), function (ServerRequestInterface $request) {
1368+
return new Response(
1369+
304,
1370+
array(),
1371+
''
1372+
);
1373+
});
1374+
1375+
$buffer = '';
1376+
$this->connection
1377+
->expects($this->any())
1378+
->method('write')
1379+
->will(
1380+
$this->returnCallback(
1381+
function ($data) use (&$buffer) {
1382+
$buffer .= $data;
1383+
}
1384+
)
1385+
);
1386+
1387+
$server->listen($this->socket);
1388+
$this->socket->emit('connection', array($this->connection));
1389+
1390+
$data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
1391+
$this->connection->emit('data', array($data));
1392+
1393+
$this->assertContainsString("HTTP/1.1 304 Not Modified\r\n", $buffer);
1394+
$this->assertNotContainsString("\r\nContent-Length: 0\r\n", $buffer);
1395+
}
1396+
1397+
public function testResponseContainsExplicitContentLengthHeaderForNotModifiedStatus()
1398+
{
1399+
$server = new StreamingServer(Factory::create(), function (ServerRequestInterface $request) {
1400+
return new Response(
1401+
304,
1402+
array('Content-Length' => 3),
1403+
''
1404+
);
1405+
});
1406+
1407+
$buffer = '';
1408+
$this->connection
1409+
->expects($this->any())
1410+
->method('write')
1411+
->will(
1412+
$this->returnCallback(
1413+
function ($data) use (&$buffer) {
1414+
$buffer .= $data;
1415+
}
1416+
)
1417+
);
1418+
1419+
$server->listen($this->socket);
1420+
$this->socket->emit('connection', array($this->connection));
1421+
1422+
$data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
1423+
$this->connection->emit('data', array($data));
1424+
1425+
$this->assertContainsString("HTTP/1.1 304 Not Modified\r\n", $buffer);
1426+
$this->assertContainsString("\r\nContent-Length: 3\r\n", $buffer);
1427+
}
1428+
13651429
public function testResponseContainsNoResponseBodyForNotModifiedStatus()
13661430
{
13671431
$server = new StreamingServer(Factory::create(), function (ServerRequestInterface $request) {

0 commit comments

Comments
 (0)