Skip to content
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

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0e88cc5
Streaming body parsers foundation
WyriHaximus Sep 27, 2016
758ad27
Added documentation to the parser interface and added cancel method t…
WyriHaximus Sep 29, 2016
304611f
Updated the raw body parser to comply wth the new parser interface
WyriHaximus Sep 29, 2016
dee32e3
Updated raw body parser test
WyriHaximus Sep 29, 2016
1ae613f
No body parser
WyriHaximus Sep 29, 2016
2dcee0f
Updated the factory, without the no body parser this PR didn't really…
WyriHaximus Sep 29, 2016
68340bb
No body parser tests
WyriHaximus Sep 29, 2016
d089c56
Changing then to done: https://github.com/reactphp/http/pull/69#discu…
WyriHaximus Oct 25, 2016
75aac07
Mark raw body parser as internal
WyriHaximus Oct 25, 2016
7a1414a
Mark no body parser as internal
WyriHaximus Oct 25, 2016
2c40605
Throw exception when content-length is missing
WyriHaximus Oct 25, 2016
a2083d2
NoBodyParser is gone as it will serve no purpose now that RawBodyPars…
WyriHaximus Oct 27, 2016
0afcef1
Clarified that the body is emitted in chunks
WyriHaximus Oct 27, 2016
8810190
Removed NoBodyParser from factory
WyriHaximus Oct 27, 2016
fe58c44
Transformed RawBodyParser to a parser that emits data chunks as they …
WyriHaximus Oct 27, 2016
5ae429e
Followed @jsor's suggestion to drop the factory method in favour of a…
WyriHaximus Nov 15, 2016
b3436bb
Factory create documentation
WyriHaximus Dec 16, 2016
f18d114
RawBodyParser::cancel docblock
WyriHaximus Dec 16, 2016
e6d5a16
Marked RawBodyParser final, it shouldn't be extended but wrapped/deco…
WyriHaximus Dec 16, 2016
2dacc3d
Added a expected arguments to streaming parser interface
WyriHaximus Dec 16, 2016
76fd4cb
Updated readme with request body parser as example
WyriHaximus Dec 16, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
21 changes: 21 additions & 0 deletions src/StreamingBodyParser/Factory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace React\Http\StreamingBodyParser;

use React\Http\Request;

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
*/
public static function create(Request $request)
{
return new RawBodyParser($request);
}
}
24 changes: 24 additions & 0 deletions src/StreamingBodyParser/ParserInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?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 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
{
/**
* Cancel parsing the request body stream.
* Parser will still emit end event to any listeners.
*
* @return void
*/
public function cancel();
}
59 changes: 59 additions & 0 deletions src/StreamingBodyParser/RawBodyParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace React\Http\StreamingBodyParser;

use Evenement\EventEmitterTrait;
use InvalidArgumentException;
use React\Http\Request;
use React\Promise\ExtendedPromiseInterface;

/**
* @internal
*/
final class RawBodyParser implements ParserInterface
{
use EventEmitterTrait;

/**
* @var Request
*/
private $request;

/**
* @param Request $request
*/
public function __construct(Request $request)
{
$this->request = $request;
$this->request->on('data', [$this, 'body']);
$this->request->on('end', [$this, 'end']);
}

/**
* @param string $data
*
* @internal
*/
public function body($data)
{
$this->emit('body', [$data]);
}

/**
* @internal
*/
public function end()
{
$this->emit('end');
}

/**
* Cancel 'parsing' the request body
*/
public function cancel()
{
$this->request->removeListener('data', [$this, 'body']);
$this->request->removeListener('end', [$this, 'end']);
$this->emit('end');
}
}
26 changes: 26 additions & 0 deletions tests/StreamingBodyParser/FactoryTest.php
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\RawBodyParser', $parser);
}
}
45 changes: 45 additions & 0 deletions tests/StreamingBodyParser/RawBodyParserTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?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 testNoBufferingLength()
{
$body = '';
$request = new Request('POST', 'http://example.com/', [], 1.1, [
'content-length' => 3,
]);
$parser = new RawBodyParser($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 = new RawBodyParser($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 = new RawBodyParser($request);
$parser->on('end', $this->expectCallableOnce());
$parser->on('body', $this->expectCallableNever());
$parser->cancel();
}
}