Skip to content

Commit 7d49a55

Browse files
committed
Add ChunkedDecoder to Server
1 parent 937aa2c commit 7d49a55

2 files changed

Lines changed: 173 additions & 11 deletions

File tree

src/Server.php

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,20 @@ public function __construct(SocketServerInterface $io)
2222
// TODO: multipart parsing
2323

2424
$parser = new RequestHeaderParser();
25-
$parser->on('headers', function (Request $request, $bodyBuffer) use ($conn, $parser, $that) {
25+
$listener = array($parser, 'feed');
26+
27+
$parser->on('headers', function (Request $request, $bodyBuffer) use ($conn, $parser, $that, $listener) {
2628
// attach remote ip to the request as metadata
2729
$request->remoteAddress = $conn->getRemoteAddress();
2830

2931
// forward pause/resume calls to underlying connection
3032
$request->on('pause', array($conn, 'pause'));
3133
$request->on('resume', array($conn, 'resume'));
34+
$conn->removeListener('data', $listener);
3235

3336
$that->handleRequest($conn, $request, $bodyBuffer);
34-
35-
$conn->removeListener('data', array($parser, 'feed'));
36-
$conn->on('end', function () use ($request) {
37-
$request->emit('end');
38-
});
39-
$conn->on('data', function ($data) use ($request) {
40-
$request->emit('data', array($data));
41-
});
4237
});
4338

44-
$listener = array($parser, 'feed');
4539
$conn->on('data', $listener);
4640
$parser->on('error', function() use ($conn, $listener, $that) {
4741
// TODO: return 400 response
@@ -62,10 +56,27 @@ public function handleRequest(ConnectionInterface $conn, Request $request, $body
6256
return;
6357
}
6458

59+
$stream = $conn;
60+
if ($request->hasHeader('Transfer-Encoding')) {
61+
$transferEncodingHeader = $request->getHeader('Transfer-Encoding');
62+
// 'chunked' must always be the final value of 'Transfer-Encoding' according to: https://tools.ietf.org/html/rfc7230#section-3.3.1
63+
if (strtolower(end($transferEncodingHeader)) === 'chunked') {
64+
$stream = new ChunkedDecoder($conn);
65+
}
66+
}
67+
68+
$stream->on('data', function ($data) use ($request) {
69+
$request->emit('data', array($data));
70+
});
71+
72+
$stream->on('end', function () use ($request) {
73+
$request->emit('end', array());
74+
});
75+
6576
$this->emit('request', array($request, $response));
6677

6778
if ($bodyBuffer !== '') {
68-
$request->emit('data', array($bodyBuffer));
79+
$conn->emit('data', array($bodyBuffer));
6980
}
7081
}
7182
}

tests/ServerTest.php

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,157 @@ public function testParserErrorEmitted()
223223
$this->connection->expects($this->never())->method('write');
224224
}
225225

226+
public function testBodyDataWillBeSendViaRequestEvent()
227+
{
228+
$server = new Server($this->socket);
229+
230+
$buffer = '';
231+
$server->on('request', function (Request $request, Response $response) use (&$buffer) {
232+
$request->on('data', function ($data) use (&$buffer) {
233+
$buffer .= $data;
234+
});
235+
});
236+
237+
$this->socket->emit('connection', array($this->connection));
238+
239+
$data = "GET / HTTP/1.1\r\n";
240+
$data .= "Host: example.com:80\r\n";
241+
$data .= "Connection: close\r\n";
242+
$data .= "Content-Length: 5\r\n";
243+
$data .= "\r\n";
244+
$data .= "hello";
245+
246+
$this->connection->emit('data', array($data));
247+
248+
$this->assertEquals('hello', $buffer);
249+
}
250+
251+
public function testChunkedEncodedRequestWillBeParsedForRequestEvent()
252+
{
253+
$server = new Server($this->socket);
254+
255+
$buffer = '';
256+
$server->on('request', function (Request $request, Response $response) use (&$buffer) {
257+
$request->on('data', function ($data) use (&$buffer) {
258+
$buffer .= $data;
259+
});
260+
});
261+
262+
$this->socket->emit('connection', array($this->connection));
263+
264+
$data = "GET / HTTP/1.1\r\n";
265+
$data .= "Host: example.com:80\r\n";
266+
$data .= "Connection: close\r\n";
267+
$data .= "Transfer-Encoding: chunked\r\n";
268+
$data .= "\r\n";
269+
$data .= "5\r\nhello\r\n";
270+
$data .= "0\r\n\r\n";
271+
272+
$this->connection->emit('data', array($data));
273+
274+
$this->assertEquals('hello', $buffer);
275+
}
276+
277+
public function testChunkedEncodedRequestAdditionalDataWontBeEmitted()
278+
{
279+
$server = new Server($this->socket);
280+
281+
$buffer = '';
282+
$server->on('request', function (Request $request, Response $response) use (&$buffer) {
283+
$request->on('data', function ($data) use (&$buffer) {
284+
$buffer .= $data;
285+
});
286+
});
287+
288+
$this->socket->emit('connection', array($this->connection));
289+
290+
$data = "GET / HTTP/1.1\r\n";
291+
$data .= "Host: example.com:80\r\n";
292+
$data .= "Connection: close\r\n";
293+
$data .= "Transfer-Encoding: chunked\r\n";
294+
$data .= "\r\n";
295+
$data .= "5\r\nhello\r\n";
296+
$data .= "0\r\n\r\n";
297+
$data .= "2\r\nhi\r\n";
298+
299+
$this->connection->emit('data', array($data));
300+
$this->assertEquals('hello', $buffer);
301+
}
302+
303+
public function testEmptyChunkedEncodedRequest()
304+
{
305+
$server = new Server($this->socket);
306+
307+
$buffer = '';
308+
$server->on('request', function (Request $request, Response $response) use (&$buffer) {
309+
$request->on('data', function ($data) use (&$buffer) {
310+
$buffer .= $data;
311+
});
312+
});
313+
314+
$this->socket->emit('connection', array($this->connection));
315+
316+
$data = "GET / HTTP/1.1\r\n";
317+
$data .= "Host: example.com:80\r\n";
318+
$data .= "Connection: close\r\n";
319+
$data .= "Transfer-Encoding: chunked\r\n";
320+
$data .= "\r\n";
321+
$data .= "0\r\n\r\n";
322+
323+
$this->connection->emit('data', array($data));
324+
$this->assertEquals('', $buffer);
325+
}
326+
327+
public function testChunkedIsUpperCase()
328+
{
329+
$server = new Server($this->socket);
330+
331+
$buffer = '';
332+
$server->on('request', function (Request $request, Response $response) use (&$buffer) {
333+
$request->on('data', function ($data) use (&$buffer) {
334+
$buffer .= $data;
335+
});
336+
});
337+
338+
$this->socket->emit('connection', array($this->connection));
339+
340+
$data = "GET / HTTP/1.1\r\n";
341+
$data .= "Host: example.com:80\r\n";
342+
$data .= "Connection: close\r\n";
343+
$data .= "Transfer-Encoding: CHUNKED\r\n";
344+
$data .= "\r\n";
345+
$data .= "5\r\nhello\r\n";
346+
$data .= "0\r\n\r\n";
347+
348+
$this->connection->emit('data', array($data));
349+
$this->assertEquals('hello', $buffer);
350+
}
351+
352+
public function testChunkedIsMixedUpperAndLowerCase()
353+
{
354+
$server = new Server($this->socket);
355+
356+
$buffer = '';
357+
$server->on('request', function (Request $request, Response $response) use (&$buffer) {
358+
$request->on('data', function ($data) use (&$buffer) {
359+
$buffer .= $data;
360+
});
361+
});
362+
363+
$this->socket->emit('connection', array($this->connection));
364+
365+
$data = "GET / HTTP/1.1\r\n";
366+
$data .= "Host: example.com:80\r\n";
367+
$data .= "Connection: close\r\n";
368+
$data .= "Transfer-Encoding: CHunKeD\r\n";
369+
$data .= "\r\n";
370+
$data .= "5\r\nhello\r\n";
371+
$data .= "0\r\n\r\n";
372+
373+
$this->connection->emit('data', array($data));
374+
$this->assertEquals('hello', $buffer);
375+
}
376+
226377
private function createGetRequest()
227378
{
228379
$data = "GET / HTTP/1.1\r\n";

0 commit comments

Comments
 (0)