-
-
Notifications
You must be signed in to change notification settings - Fork 145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Streaming body parsers foundation #69
Changes from 7 commits
0e88cc5
758ad27
304611f
dee32e3
1ae613f
2dcee0f
68340bb
d089c56
75aac07
7a1414a
2c40605
a2083d2
0afcef1
8810190
fe58c44
5ae429e
b3436bb
f18d114
e6d5a16
2dacc3d
76fd4cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
namespace React\Http\StreamingBodyParser; | ||
|
||
use React\Http\Request; | ||
|
||
class Factory | ||
{ | ||
/** | ||
* @param Request $request | ||
* @return ParserInterface | ||
*/ | ||
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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<?php | ||
|
||
namespace React\Http\StreamingBodyParser; | ||
|
||
use Evenement\EventEmitterTrait; | ||
use React\Http\Request; | ||
|
||
class NoBodyParser implements ParserInterface | ||
{ | ||
use EventEmitterTrait; | ||
|
||
/** | ||
* @var Request | ||
*/ | ||
private $request; | ||
|
||
/** | ||
* @param Request $request | ||
* @return ParserInterface | ||
*/ | ||
public static function create(Request $request) | ||
{ | ||
return new static($request); | ||
} | ||
|
||
/** | ||
* @param Request $request | ||
*/ | ||
private function __construct(Request $request) | ||
{ | ||
$this->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'); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
namespace React\Http\StreamingBodyParser; | ||
|
||
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 | ||
{ | ||
/** | ||
* 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(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
<?php | ||
|
||
namespace React\Http\StreamingBodyParser; | ||
|
||
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 | ||
*/ | ||
private function __construct(Request $request) | ||
{ | ||
$headers = $request->getHeaders(); | ||
$headers = array_change_key_case($headers, CASE_LOWER); | ||
|
||
$this->promise = ContentLengthBufferedSink::createPromise( | ||
$request, | ||
$headers['content-length'] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if there is no (or an invalid) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Marked the class internal (just as |
||
)->then([$this, 'finish']); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally, this should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changing it to be |
||
} | ||
|
||
/** | ||
* @param string $buffer | ||
* | ||
* @internal | ||
*/ | ||
public function finish($buffer) | ||
{ | ||
$this->emit('body', [$buffer]); | ||
$this->emit('end'); | ||
} | ||
|
||
public function cancel() | ||
{ | ||
$this->promise->cancel(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
namespace React\Tests\Http\StreamingBodyParser; | ||
|
||
use React\Http\StreamingBodyParser\Factory; | ||
use React\Http\Request; | ||
use React\Tests\Http\TestCase; | ||
|
||
class FactoryTest extends TestCase | ||
{ | ||
public function testContentLength() | ||
{ | ||
$request = new Request('POST', 'http://example.com/', [], 1.1, [ | ||
'content-length' => 123, | ||
]); | ||
$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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
namespace React\Tests\Http\StreamingBodyParser; | ||
|
||
use React\Http\Request; | ||
use React\Http\StreamingBodyParser\NoBodyParser; | ||
use React\Tests\Http\TestCase; | ||
|
||
class NoBodyParserTest extends TestCase | ||
{ | ||
public function testRequestEnd() | ||
{ | ||
$request = new Request('POST', 'http://example.com/'); | ||
$parser = NoBodyParser::create($request); | ||
$parser->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(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<?php | ||
|
||
namespace React\Tests\Http\StreamingBodyParser; | ||
|
||
use React\Http\StreamingBodyParser\RawBodyParser; | ||
use React\Http\Request; | ||
use React\Tests\Http\TestCase; | ||
|
||
class RawBodyParserTest extends TestCase | ||
{ | ||
public function testNoContentLength() | ||
{ | ||
$body = ''; | ||
$request = new Request('POST', 'http://example.com/', [], 1.1, [ | ||
'content-length' => 3, | ||
]); | ||
$parser = RawBodyParser::create($request); | ||
$parser->on('body', function ($rawBody) use (&$body) { | ||
$body = $rawBody; | ||
}); | ||
$request->emit('data', ['abc']); | ||
$this->assertSame('abc', $body); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the parser implementations are now
@internal
, i think this method can be removed from the interface and the implementations and simple constructor based instantiantion can be used inFactory
.