From 0e88cc55b784d55e462b9484124cf10e2b281616 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Wed, 28 Sep 2016 01:20:48 +0200 Subject: [PATCH 01/21] Streaming body parsers foundation --- src/StreamingBodyParser/Factory.php | 17 ++++++++++ src/StreamingBodyParser/ParserInterface.php | 11 ++++++ src/StreamingBodyParser/RawBodyParser.php | 34 +++++++++++++++++++ tests/StreamingBodyParser/FactoryTest.php | 19 +++++++++++ .../StreamingBodyParser/RawBodyParserTest.php | 24 +++++++++++++ 5 files changed, 105 insertions(+) create mode 100644 src/StreamingBodyParser/Factory.php create mode 100644 src/StreamingBodyParser/ParserInterface.php create mode 100644 src/StreamingBodyParser/RawBodyParser.php create mode 100644 tests/StreamingBodyParser/FactoryTest.php create mode 100644 tests/StreamingBodyParser/RawBodyParserTest.php diff --git a/src/StreamingBodyParser/Factory.php b/src/StreamingBodyParser/Factory.php new file mode 100644 index 00000000..270a223a --- /dev/null +++ b/src/StreamingBodyParser/Factory.php @@ -0,0 +1,17 @@ +getHeaders(); + $headers = array_change_key_case($headers, CASE_LOWER); + + ContentLengthBufferedSink::createPromise( + $request, + $headers['content-length'] + )->then([$this, 'finish']); + } + + /** + * @param string $buffer + */ + public function finish($buffer) + { + $this->emit('body', [$buffer]); + $this->emit('end'); + } +} diff --git a/tests/StreamingBodyParser/FactoryTest.php b/tests/StreamingBodyParser/FactoryTest.php new file mode 100644 index 00000000..106e07db --- /dev/null +++ b/tests/StreamingBodyParser/FactoryTest.php @@ -0,0 +1,19 @@ + 123, + ]); + $parser = Factory::create($request); + $this->assertInstanceOf('React\Http\StreamingBodyParser\RawBodyParser', $parser); + } +} diff --git a/tests/StreamingBodyParser/RawBodyParserTest.php b/tests/StreamingBodyParser/RawBodyParserTest.php new file mode 100644 index 00000000..c9a25a83 --- /dev/null +++ b/tests/StreamingBodyParser/RawBodyParserTest.php @@ -0,0 +1,24 @@ + 3, + ]); + $parser = new RawBodyParser($request); + $parser->on('body', function ($rawBody) use (&$body) { + $body = $rawBody; + }); + $request->emit('data', ['abc']); + $this->assertSame('abc', $body); + } +} From 758ad272cbb404c0a07a4e46f47ecc9da83163be Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 29 Sep 2016 17:24:20 +0200 Subject: [PATCH 02/21] Added documentation to the parser interface and added cancel method to cancel parsing of a request body stream --- src/StreamingBodyParser/ParserInterface.php | 23 ++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/StreamingBodyParser/ParserInterface.php b/src/StreamingBodyParser/ParserInterface.php index 8539b1c1..ffcd0910 100644 --- a/src/StreamingBodyParser/ParserInterface.php +++ b/src/StreamingBodyParser/ParserInterface.php @@ -5,7 +5,28 @@ use Evenement\EventEmitterInterface; use React\Http\Request; +/** + * Parser can emit the following events: + * end - When done or canceled (required) + * body - Raw request body (optional) + * post - Post field (optional) + * file - Uploaded file (optional) + */ interface ParserInterface extends EventEmitterInterface { - public function __construct(Request $request); + /** + * Factory method creating the parser. + * + * @param Request $request + * @return ParserInterface + */ + public static function create(Request $request); + + /** + * Cancel parsing the request body stream. + * Parser will still emit end event to any listeners. + * + * @return void + */ + public function cancel(); } From 304611fe74d94cc91bbb2f5ede1eb97271507f37 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 29 Sep 2016 17:32:43 +0200 Subject: [PATCH 03/21] Updated the raw body parser to comply wth the new parser interface --- src/StreamingBodyParser/RawBodyParser.php | 26 +++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/StreamingBodyParser/RawBodyParser.php b/src/StreamingBodyParser/RawBodyParser.php index b3681df1..a186ca4e 100644 --- a/src/StreamingBodyParser/RawBodyParser.php +++ b/src/StreamingBodyParser/RawBodyParser.php @@ -4,20 +4,35 @@ use Evenement\EventEmitterTrait; use React\Http\Request; +use React\Promise\ExtendedPromiseInterface; class RawBodyParser implements ParserInterface { use EventEmitterTrait; + /** + * @var ExtendedPromiseInterface + */ + private $promise; + + /** + * @param Request $request + * @return ParserInterface + */ + public static function create(Request $request) + { + return new static($request); + } + /** * @param Request $request */ - public function __construct(Request $request) + private function __construct(Request $request) { $headers = $request->getHeaders(); $headers = array_change_key_case($headers, CASE_LOWER); - ContentLengthBufferedSink::createPromise( + $this->promise = ContentLengthBufferedSink::createPromise( $request, $headers['content-length'] )->then([$this, 'finish']); @@ -25,10 +40,17 @@ public function __construct(Request $request) /** * @param string $buffer + * + * @internal */ public function finish($buffer) { $this->emit('body', [$buffer]); $this->emit('end'); } + + public function cancel() + { + $this->promise->cancel(); + } } From dee32e31e1b3e0db42fd5b3ab6eb2f821a5b27e7 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 29 Sep 2016 17:36:41 +0200 Subject: [PATCH 04/21] Updated raw body parser test --- tests/StreamingBodyParser/RawBodyParserTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/StreamingBodyParser/RawBodyParserTest.php b/tests/StreamingBodyParser/RawBodyParserTest.php index c9a25a83..3a2c0e4f 100644 --- a/tests/StreamingBodyParser/RawBodyParserTest.php +++ b/tests/StreamingBodyParser/RawBodyParserTest.php @@ -14,7 +14,7 @@ public function testNoContentLength() $request = new Request('POST', 'http://example.com/', [], 1.1, [ 'content-length' => 3, ]); - $parser = new RawBodyParser($request); + $parser = RawBodyParser::create($request); $parser->on('body', function ($rawBody) use (&$body) { $body = $rawBody; }); From 1ae613fac6e59db2a64d0f679e41eb88c7f54244 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 29 Sep 2016 17:39:15 +0200 Subject: [PATCH 05/21] No body parser --- src/StreamingBodyParser/NoBodyParser.php | 48 ++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/StreamingBodyParser/NoBodyParser.php diff --git a/src/StreamingBodyParser/NoBodyParser.php b/src/StreamingBodyParser/NoBodyParser.php new file mode 100644 index 00000000..da7256ee --- /dev/null +++ b/src/StreamingBodyParser/NoBodyParser.php @@ -0,0 +1,48 @@ +request = $request; + $this->request->on('end', [$this, 'end']); + } + + public function cancel() + { + $this->request->removeListener('end', [$this, 'end']); + $this->emit('end'); + } + + /** + * @internal + */ + public function end() + { + $this->emit('end'); + } +} From 2dcee0ff9c97865003ade4817b17d1bbb78aad34 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 29 Sep 2016 17:39:59 +0200 Subject: [PATCH 06/21] Updated the factory, without the no body parser this PR didn't really make sense in retrospec --- src/StreamingBodyParser/Factory.php | 11 ++++++++++- tests/StreamingBodyParser/FactoryTest.php | 9 ++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/StreamingBodyParser/Factory.php b/src/StreamingBodyParser/Factory.php index 270a223a..f360a9be 100644 --- a/src/StreamingBodyParser/Factory.php +++ b/src/StreamingBodyParser/Factory.php @@ -12,6 +12,15 @@ class Factory */ public static function create(Request $request) { - return new RawBodyParser($request); + $headers = $request->getHeaders(); + $headers = array_change_key_case($headers, CASE_LOWER); + + if ( + !isset($headers['content-length']) + ) { + return NoBodyParser::create($request); + } + + return RawBodyParser::create($request); } } diff --git a/tests/StreamingBodyParser/FactoryTest.php b/tests/StreamingBodyParser/FactoryTest.php index 106e07db..73c7d252 100644 --- a/tests/StreamingBodyParser/FactoryTest.php +++ b/tests/StreamingBodyParser/FactoryTest.php @@ -8,7 +8,7 @@ class FactoryTest extends TestCase { - public function testNoContentType() + public function testContentLength() { $request = new Request('POST', 'http://example.com/', [], 1.1, [ 'content-length' => 123, @@ -16,4 +16,11 @@ public function testNoContentType() $parser = Factory::create($request); $this->assertInstanceOf('React\Http\StreamingBodyParser\RawBodyParser', $parser); } + + public function testNoHeaders() + { + $request = new Request('POST', 'http://example.com/'); + $parser = Factory::create($request); + $this->assertInstanceOf('React\Http\StreamingBodyParser\NoBodyParser', $parser); + } } From 68340bb2c663e5a611ec756394bf440626b5b1e2 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 29 Sep 2016 17:47:38 +0200 Subject: [PATCH 07/21] No body parser tests --- .../StreamingBodyParser/NoBodyParserTest.php | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/StreamingBodyParser/NoBodyParserTest.php diff --git a/tests/StreamingBodyParser/NoBodyParserTest.php b/tests/StreamingBodyParser/NoBodyParserTest.php new file mode 100644 index 00000000..ee4163c7 --- /dev/null +++ b/tests/StreamingBodyParser/NoBodyParserTest.php @@ -0,0 +1,26 @@ +on('end', $this->expectCallableOnce()); + $request->close(); + } + + public function testCancelParser() + { + $request = new Request('POST', 'http://example.com/'); + $parser = NoBodyParser::create($request); + $parser->on('end', $this->expectCallableOnce()); + $parser->cancel(); + } +} From d089c564d9861c846b44e1726873e760add1da1b Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 25 Oct 2016 17:33:21 +0200 Subject: [PATCH 08/21] Changing then to done: https://github.com/reactphp/http/pull/69#discussion_r84625161 --- src/StreamingBodyParser/RawBodyParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StreamingBodyParser/RawBodyParser.php b/src/StreamingBodyParser/RawBodyParser.php index a186ca4e..f7c89d09 100644 --- a/src/StreamingBodyParser/RawBodyParser.php +++ b/src/StreamingBodyParser/RawBodyParser.php @@ -35,7 +35,7 @@ private function __construct(Request $request) $this->promise = ContentLengthBufferedSink::createPromise( $request, $headers['content-length'] - )->then([$this, 'finish']); + )->done([$this, 'finish']); } /** From 75aac076f2542ef766ba67fcf62ad0a536c3152a Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 25 Oct 2016 17:34:23 +0200 Subject: [PATCH 09/21] Mark raw body parser as internal --- src/StreamingBodyParser/RawBodyParser.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/StreamingBodyParser/RawBodyParser.php b/src/StreamingBodyParser/RawBodyParser.php index f7c89d09..bd45adcb 100644 --- a/src/StreamingBodyParser/RawBodyParser.php +++ b/src/StreamingBodyParser/RawBodyParser.php @@ -6,6 +6,9 @@ use React\Http\Request; use React\Promise\ExtendedPromiseInterface; +/** + * @internal + */ class RawBodyParser implements ParserInterface { use EventEmitterTrait; From 7a1414a59052177e41df4c6082726c9a02420cc4 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 25 Oct 2016 17:34:41 +0200 Subject: [PATCH 10/21] Mark no body parser as internal --- src/StreamingBodyParser/NoBodyParser.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/StreamingBodyParser/NoBodyParser.php b/src/StreamingBodyParser/NoBodyParser.php index da7256ee..e607ec2e 100644 --- a/src/StreamingBodyParser/NoBodyParser.php +++ b/src/StreamingBodyParser/NoBodyParser.php @@ -5,6 +5,9 @@ use Evenement\EventEmitterTrait; use React\Http\Request; +/** + * @internal + */ class NoBodyParser implements ParserInterface { use EventEmitterTrait; From 2c406055f44d799e98c6c17c6123b8b3755d35e7 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 25 Oct 2016 17:36:08 +0200 Subject: [PATCH 11/21] Throw exception when content-length is missing --- src/StreamingBodyParser/RawBodyParser.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/StreamingBodyParser/RawBodyParser.php b/src/StreamingBodyParser/RawBodyParser.php index bd45adcb..adf6b776 100644 --- a/src/StreamingBodyParser/RawBodyParser.php +++ b/src/StreamingBodyParser/RawBodyParser.php @@ -3,6 +3,7 @@ namespace React\Http\StreamingBodyParser; use Evenement\EventEmitterTrait; +use InvalidArgumentException; use React\Http\Request; use React\Promise\ExtendedPromiseInterface; @@ -35,6 +36,10 @@ private function __construct(Request $request) $headers = $request->getHeaders(); $headers = array_change_key_case($headers, CASE_LOWER); + if (!isset($headers['content-length'])) { + throw new InvalidArgumentException('content-length header missing on request'); + } + $this->promise = ContentLengthBufferedSink::createPromise( $request, $headers['content-length'] From a2083d27668351a101e46b336b3610817b45c067 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 27 Oct 2016 08:00:54 +0200 Subject: [PATCH 12/21] NoBodyParser is gone as it will serve no purpose now that RawBodyParser is going to emit body events when data comes in and there for there will always be access to the raw body when needed --- src/StreamingBodyParser/NoBodyParser.php | 51 ------------------- .../StreamingBodyParser/NoBodyParserTest.php | 26 ---------- 2 files changed, 77 deletions(-) delete mode 100644 src/StreamingBodyParser/NoBodyParser.php delete mode 100644 tests/StreamingBodyParser/NoBodyParserTest.php diff --git a/src/StreamingBodyParser/NoBodyParser.php b/src/StreamingBodyParser/NoBodyParser.php deleted file mode 100644 index e607ec2e..00000000 --- a/src/StreamingBodyParser/NoBodyParser.php +++ /dev/null @@ -1,51 +0,0 @@ -request = $request; - $this->request->on('end', [$this, 'end']); - } - - public function cancel() - { - $this->request->removeListener('end', [$this, 'end']); - $this->emit('end'); - } - - /** - * @internal - */ - public function end() - { - $this->emit('end'); - } -} diff --git a/tests/StreamingBodyParser/NoBodyParserTest.php b/tests/StreamingBodyParser/NoBodyParserTest.php deleted file mode 100644 index ee4163c7..00000000 --- a/tests/StreamingBodyParser/NoBodyParserTest.php +++ /dev/null @@ -1,26 +0,0 @@ -on('end', $this->expectCallableOnce()); - $request->close(); - } - - public function testCancelParser() - { - $request = new Request('POST', 'http://example.com/'); - $parser = NoBodyParser::create($request); - $parser->on('end', $this->expectCallableOnce()); - $parser->cancel(); - } -} From 0afcef160415cd48c1212ad4ffea1a22e531e0b9 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 27 Oct 2016 17:34:10 +0200 Subject: [PATCH 13/21] Clarified that the body is emitted in chunks --- src/StreamingBodyParser/ParserInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StreamingBodyParser/ParserInterface.php b/src/StreamingBodyParser/ParserInterface.php index ffcd0910..90b99935 100644 --- a/src/StreamingBodyParser/ParserInterface.php +++ b/src/StreamingBodyParser/ParserInterface.php @@ -8,7 +8,7 @@ /** * Parser can emit the following events: * end - When done or canceled (required) - * body - Raw request body (optional) + * body - Raw request body chunks as they come in (optional) * post - Post field (optional) * file - Uploaded file (optional) */ From 8810190cc5e62e65377ac6314503796a9a948af9 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 27 Oct 2016 17:38:10 +0200 Subject: [PATCH 14/21] Removed NoBodyParser from factory --- src/StreamingBodyParser/Factory.php | 9 --------- tests/StreamingBodyParser/FactoryTest.php | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/StreamingBodyParser/Factory.php b/src/StreamingBodyParser/Factory.php index f360a9be..444d358f 100644 --- a/src/StreamingBodyParser/Factory.php +++ b/src/StreamingBodyParser/Factory.php @@ -12,15 +12,6 @@ class Factory */ public static function create(Request $request) { - $headers = $request->getHeaders(); - $headers = array_change_key_case($headers, CASE_LOWER); - - if ( - !isset($headers['content-length']) - ) { - return NoBodyParser::create($request); - } - return RawBodyParser::create($request); } } diff --git a/tests/StreamingBodyParser/FactoryTest.php b/tests/StreamingBodyParser/FactoryTest.php index 73c7d252..138f1985 100644 --- a/tests/StreamingBodyParser/FactoryTest.php +++ b/tests/StreamingBodyParser/FactoryTest.php @@ -21,6 +21,6 @@ public function testNoHeaders() { $request = new Request('POST', 'http://example.com/'); $parser = Factory::create($request); - $this->assertInstanceOf('React\Http\StreamingBodyParser\NoBodyParser', $parser); + $this->assertInstanceOf('React\Http\StreamingBodyParser\RawBodyParser', $parser); } } From fe58c44a7eafd90af77bb036ece0b6c7855912f1 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 27 Oct 2016 17:42:53 +0200 Subject: [PATCH 15/21] Transformed RawBodyParser to a parser that emits data chunks as they come in --- src/StreamingBodyParser/RawBodyParser.php | 35 ++++++++++--------- .../StreamingBodyParser/RawBodyParserTest.php | 23 +++++++++++- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/StreamingBodyParser/RawBodyParser.php b/src/StreamingBodyParser/RawBodyParser.php index adf6b776..75e84243 100644 --- a/src/StreamingBodyParser/RawBodyParser.php +++ b/src/StreamingBodyParser/RawBodyParser.php @@ -15,9 +15,9 @@ class RawBodyParser implements ParserInterface use EventEmitterTrait; /** - * @var ExtendedPromiseInterface + * @var Request */ - private $promise; + private $request; /** * @param Request $request @@ -33,32 +33,33 @@ public static function create(Request $request) */ private function __construct(Request $request) { - $headers = $request->getHeaders(); - $headers = array_change_key_case($headers, CASE_LOWER); - - if (!isset($headers['content-length'])) { - throw new InvalidArgumentException('content-length header missing on request'); - } - - $this->promise = ContentLengthBufferedSink::createPromise( - $request, - $headers['content-length'] - )->done([$this, 'finish']); + $this->request = $request; + $this->request->on('data', [$this, 'body']); + $this->request->on('end', [$this, 'end']); } /** - * @param string $buffer + * @param string $data * * @internal */ - public function finish($buffer) + public function body($data) + { + $this->emit('body', [$data]); + } + + /** + * @internal + */ + public function end() { - $this->emit('body', [$buffer]); $this->emit('end'); } public function cancel() { - $this->promise->cancel(); + $this->request->removeListener('data', [$this, 'body']); + $this->request->removeListener('end', [$this, 'end']); + $this->emit('end'); } } diff --git a/tests/StreamingBodyParser/RawBodyParserTest.php b/tests/StreamingBodyParser/RawBodyParserTest.php index 3a2c0e4f..18dc5f9f 100644 --- a/tests/StreamingBodyParser/RawBodyParserTest.php +++ b/tests/StreamingBodyParser/RawBodyParserTest.php @@ -8,17 +8,38 @@ class RawBodyParserTest extends TestCase { - public function testNoContentLength() + public function testNoBufferingLength() { $body = ''; $request = new Request('POST', 'http://example.com/', [], 1.1, [ 'content-length' => 3, ]); $parser = RawBodyParser::create($request); + $parser->on('end', $this->expectCallableNever()); $parser->on('body', function ($rawBody) use (&$body) { $body = $rawBody; }); $request->emit('data', ['abc']); $this->assertSame('abc', $body); + $request->emit('data', ['def']); + $this->assertSame('def', $body); + } + + public function testEndForward() + { + $request = new Request('POST', 'http://example.com/'); + $parser = RawBodyParser::create($request); + $parser->on('end', $this->expectCallableOnce()); + $parser->on('body', $this->expectCallableNever()); + $request->emit('end'); + } + + public function testEndOnCancel() + { + $request = new Request('POST', 'http://example.com/'); + $parser = RawBodyParser::create($request); + $parser->on('end', $this->expectCallableOnce()); + $parser->on('body', $this->expectCallableNever()); + $parser->cancel(); } } From 5ae429ebd99543782714358371c2a3562b55bef6 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 15 Nov 2016 07:55:12 +0100 Subject: [PATCH 16/21] Followed @jsor's suggestion to drop the factory method in favour of a public constructor now that parsers are marked @internal at https://github.com/reactphp/http/pull/69#discussion_r85053935 --- src/StreamingBodyParser/Factory.php | 2 +- src/StreamingBodyParser/ParserInterface.php | 8 -------- src/StreamingBodyParser/RawBodyParser.php | 11 +---------- tests/StreamingBodyParser/RawBodyParserTest.php | 6 +++--- 4 files changed, 5 insertions(+), 22 deletions(-) diff --git a/src/StreamingBodyParser/Factory.php b/src/StreamingBodyParser/Factory.php index 444d358f..270a223a 100644 --- a/src/StreamingBodyParser/Factory.php +++ b/src/StreamingBodyParser/Factory.php @@ -12,6 +12,6 @@ class Factory */ public static function create(Request $request) { - return RawBodyParser::create($request); + return new RawBodyParser($request); } } diff --git a/src/StreamingBodyParser/ParserInterface.php b/src/StreamingBodyParser/ParserInterface.php index 90b99935..43075f9a 100644 --- a/src/StreamingBodyParser/ParserInterface.php +++ b/src/StreamingBodyParser/ParserInterface.php @@ -14,14 +14,6 @@ */ interface ParserInterface extends EventEmitterInterface { - /** - * Factory method creating the parser. - * - * @param Request $request - * @return ParserInterface - */ - public static function create(Request $request); - /** * Cancel parsing the request body stream. * Parser will still emit end event to any listeners. diff --git a/src/StreamingBodyParser/RawBodyParser.php b/src/StreamingBodyParser/RawBodyParser.php index 75e84243..3e1c9e7f 100644 --- a/src/StreamingBodyParser/RawBodyParser.php +++ b/src/StreamingBodyParser/RawBodyParser.php @@ -21,17 +21,8 @@ class RawBodyParser implements ParserInterface /** * @param Request $request - * @return ParserInterface */ - public static function create(Request $request) - { - return new static($request); - } - - /** - * @param Request $request - */ - private function __construct(Request $request) + public function __construct(Request $request) { $this->request = $request; $this->request->on('data', [$this, 'body']); diff --git a/tests/StreamingBodyParser/RawBodyParserTest.php b/tests/StreamingBodyParser/RawBodyParserTest.php index 18dc5f9f..8202b9f8 100644 --- a/tests/StreamingBodyParser/RawBodyParserTest.php +++ b/tests/StreamingBodyParser/RawBodyParserTest.php @@ -14,7 +14,7 @@ public function testNoBufferingLength() $request = new Request('POST', 'http://example.com/', [], 1.1, [ 'content-length' => 3, ]); - $parser = RawBodyParser::create($request); + $parser = new RawBodyParser($request); $parser->on('end', $this->expectCallableNever()); $parser->on('body', function ($rawBody) use (&$body) { $body = $rawBody; @@ -28,7 +28,7 @@ public function testNoBufferingLength() public function testEndForward() { $request = new Request('POST', 'http://example.com/'); - $parser = RawBodyParser::create($request); + $parser = new RawBodyParser($request); $parser->on('end', $this->expectCallableOnce()); $parser->on('body', $this->expectCallableNever()); $request->emit('end'); @@ -37,7 +37,7 @@ public function testEndForward() public function testEndOnCancel() { $request = new Request('POST', 'http://example.com/'); - $parser = RawBodyParser::create($request); + $parser = new RawBodyParser($request); $parser->on('end', $this->expectCallableOnce()); $parser->on('body', $this->expectCallableNever()); $parser->cancel(); From b3436bb400e0d761fc85bcfc4caa8eb67446cbda Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Fri, 16 Dec 2016 17:21:06 +0100 Subject: [PATCH 17/21] Factory create documentation --- src/StreamingBodyParser/Factory.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/StreamingBodyParser/Factory.php b/src/StreamingBodyParser/Factory.php index 270a223a..9a4d9fbe 100644 --- a/src/StreamingBodyParser/Factory.php +++ b/src/StreamingBodyParser/Factory.php @@ -7,6 +7,10 @@ class Factory { /** + * Creates the most suitable streaming body parser for the given request. + * It can return the following parsers: + * * RawBodyParser - Emit raw body chunks as they come in. This is the default parser. + * * @param Request $request * @return ParserInterface */ From f18d114c143824fd08e4271f4e29f601ea6517c1 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Fri, 16 Dec 2016 17:24:46 +0100 Subject: [PATCH 18/21] RawBodyParser::cancel docblock --- src/StreamingBodyParser/RawBodyParser.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/StreamingBodyParser/RawBodyParser.php b/src/StreamingBodyParser/RawBodyParser.php index 3e1c9e7f..6cba5ac8 100644 --- a/src/StreamingBodyParser/RawBodyParser.php +++ b/src/StreamingBodyParser/RawBodyParser.php @@ -47,6 +47,9 @@ public function end() $this->emit('end'); } + /** + * Cancel 'parsing' the request body + */ public function cancel() { $this->request->removeListener('data', [$this, 'body']); From e6d5a1687a02dab5902dbf372fce263a55e3a13c Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Fri, 16 Dec 2016 17:25:13 +0100 Subject: [PATCH 19/21] Marked RawBodyParser final, it shouldn't be extended but wrapped/decorated instead --- src/StreamingBodyParser/RawBodyParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StreamingBodyParser/RawBodyParser.php b/src/StreamingBodyParser/RawBodyParser.php index 6cba5ac8..c8a1545e 100644 --- a/src/StreamingBodyParser/RawBodyParser.php +++ b/src/StreamingBodyParser/RawBodyParser.php @@ -10,7 +10,7 @@ /** * @internal */ -class RawBodyParser implements ParserInterface +final class RawBodyParser implements ParserInterface { use EventEmitterTrait; From 2dacc3dfa3d4267e8676e46cf4198622bc0e41e7 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Fri, 16 Dec 2016 17:36:12 +0100 Subject: [PATCH 20/21] Added a expected arguments to streaming parser interface --- src/StreamingBodyParser/ParserInterface.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/StreamingBodyParser/ParserInterface.php b/src/StreamingBodyParser/ParserInterface.php index 43075f9a..25248bf3 100644 --- a/src/StreamingBodyParser/ParserInterface.php +++ b/src/StreamingBodyParser/ParserInterface.php @@ -8,9 +8,9 @@ /** * Parser can emit the following events: * end - When done or canceled (required) - * body - Raw request body chunks as they come in (optional) - * post - Post field (optional) - * file - Uploaded file (optional) + * body - Raw request body chunks as they come in, with $bodyString as argument (optional) + * post - Post field, with $filedName, $fieldValue as arguments (optional) + * file - Uploaded file with $filedName, $fileObject as arguments (optional) */ interface ParserInterface extends EventEmitterInterface { From 76fd4cbcbb9d3d57714538c64fbd3369e34aa9b3 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Fri, 16 Dec 2016 17:40:07 +0100 Subject: [PATCH 21/21] Updated readme with request body parser as example --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 9bad8ad4..a9b80583 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,31 @@ $loop->run(); ``` See also the [examples](examples). + +## Request body parsing example + +As of `v0.5` `react/http` supports request body parsing and comes with a handy factory: + +```php +$loop = React\EventLoop\Factory::create(); +$socket = new React\Socket\Server($loop); + +$http = new React\Http\Server($socket); +$http->on('request', function ($request, $response) { + $parser = React\Http\StreamingBodyParser\Factory::create($request); + $parser->on('body', function ($bodyString) {}); + $parser->on('post', function ($fieldName, $fileValue) {}); + $parser->on('file', function ($fieldName, $fileObject) {}); + $parser->on('end', function () use ($response) { + $response->writeHead(200, array('Content-Type' => 'text/plain')); + $response->end("Hello World!\n"); + }); +}); + +$socket->listen(1337); +$loop->run(); +``` + +### Current supported request body parsers + +* RawBodyParser - Emit raw body chunks as they come in, no parsing for `post` or `file`. This is the default parser.