Skip to content

Commit 2d200a8

Browse files
committed
Improve assigning Content-Length for 304 Not Modified response
1 parent 80e4593 commit 2d200a8

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
@@ -1291,6 +1291,70 @@ function ($data) use (&$buffer) {
12911291
$this->assertNotContainsString("bye", $buffer);
12921292
}
12931293

1294+
public function testResponseContainsNoContentLengthHeaderForNotModifiedStatus()
1295+
{
1296+
$server = new StreamingServer(Factory::create(), function (ServerRequestInterface $request) {
1297+
return new Response(
1298+
304,
1299+
array(),
1300+
''
1301+
);
1302+
});
1303+
1304+
$buffer = '';
1305+
$this->connection
1306+
->expects($this->any())
1307+
->method('write')
1308+
->will(
1309+
$this->returnCallback(
1310+
function ($data) use (&$buffer) {
1311+
$buffer .= $data;
1312+
}
1313+
)
1314+
);
1315+
1316+
$server->listen($this->socket);
1317+
$this->socket->emit('connection', array($this->connection));
1318+
1319+
$data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
1320+
$this->connection->emit('data', array($data));
1321+
1322+
$this->assertContainsString("HTTP/1.1 304 Not Modified\r\n", $buffer);
1323+
$this->assertNotContainsString("\r\nContent-Length: 0\r\n", $buffer);
1324+
}
1325+
1326+
public function testResponseContainsExplicitContentLengthHeaderForNotModifiedStatus()
1327+
{
1328+
$server = new StreamingServer(Factory::create(), function (ServerRequestInterface $request) {
1329+
return new Response(
1330+
304,
1331+
array('Content-Length' => 3),
1332+
''
1333+
);
1334+
});
1335+
1336+
$buffer = '';
1337+
$this->connection
1338+
->expects($this->any())
1339+
->method('write')
1340+
->will(
1341+
$this->returnCallback(
1342+
function ($data) use (&$buffer) {
1343+
$buffer .= $data;
1344+
}
1345+
)
1346+
);
1347+
1348+
$server->listen($this->socket);
1349+
$this->socket->emit('connection', array($this->connection));
1350+
1351+
$data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
1352+
$this->connection->emit('data', array($data));
1353+
1354+
$this->assertContainsString("HTTP/1.1 304 Not Modified\r\n", $buffer);
1355+
$this->assertContainsString("\r\nContent-Length: 3\r\n", $buffer);
1356+
}
1357+
12941358
public function testResponseContainsNoResponseBodyForNotModifiedStatus()
12951359
{
12961360
$server = new StreamingServer(Factory::create(), function (ServerRequestInterface $request) {

0 commit comments

Comments
 (0)