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

[WIP] Streaming parser bufferedsink #73

Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
bf97310
Streaming parser bufferedsink
WyriHaximus Oct 6, 2016
9207588
BodyBufferedSink: https://github.com/reactphp/http/pull/73#issuecomme…
WyriHaximus Oct 26, 2016
b046c84
Removed old vars I forgot to removed :X
WyriHaximus Oct 26, 2016
0f54149
Correct PostBufferedSinkTest class name
WyriHaximus Oct 26, 2016
b775b96
$result now contains the body not an array with different types of da…
WyriHaximus Oct 26, 2016
ee9039f
PostBufferedSink: https://github.com/reactphp/http/pull/73#issuecomme…
WyriHaximus Oct 26, 2016
11fb097
Removed unused uses
WyriHaximus Oct 26, 2016
0619c14
Then => done: https://github.com/reactphp/http/pull/73#discussion_r84…
WyriHaximus Oct 26, 2016
64ce2d6
Corrected return comment for BodyBufferedSink::createPromise
WyriHaximus Oct 26, 2016
b1d8e0e
Corrected return comment for PostBufferedSink::createPromise
WyriHaximus Oct 26, 2016
75facbb
Call the correct sink for body buffered sink test
WyriHaximus Oct 26, 2016
14c2111
Call the correct sink for post buffered sink test
WyriHaximus Oct 26, 2016
21e943a
FileBufferedSink: https://github.com/reactphp/http/pull/73#issuecomme…
WyriHaximus Oct 26, 2016
2437eef
Append additional body calls
WyriHaximus Oct 27, 2016
1d9a18d
NoBodyParser is no more
WyriHaximus Oct 27, 2016
e8c452b
Resource BufferedSink as a handy wrapper around the other parser sink…
WyriHaximus Oct 27, 2016
c2603b8
Type in PostBufferedSinkTest https://github.com/reactphp/http/pull/73…
WyriHaximus Oct 27, 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
32 changes: 32 additions & 0 deletions src/StreamingBodyParser/BodyBufferedSink.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace React\Http\StreamingBodyParser;

use React\Http\File;
use React\Promise;

class BodyBufferedSink
{
/**
* @param ParserInterface $parser
* @return Promise\PromiseInterface
*/
public static function createPromise(ParserInterface $parser)
{
if ($parser instanceof NoBodyParser) {
return Promise\resolve('');
}

$deferred = new Promise\Deferred();
$body = '';

$parser->on('body', function ($rawBody) use (&$body) {
$body = $rawBody;
});
$parser->on('end', function () use ($deferred, &$body) {
$deferred->resolve($body);
});

return $deferred->promise();
}
}
90 changes: 90 additions & 0 deletions src/StreamingBodyParser/BufferedSink.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

namespace React\Http\StreamingBodyParser;

use React\Http\File;
use React\Promise\Deferred;
use React\Promise\PromiseInterface;
use React\Stream\BufferedSink as StreamBufferedSink;

class BufferedSink
{
/**
* @param ParserInterface $parser
* @return PromiseInterface
*/
public static function createPromise(ParserInterface $parser)
{
if ($parser instanceof NoBodyParser) {
return \React\Promise\resolve([
'post' => [],
'files' => [],
'body' => '',
]);
}

$deferred = new Deferred();
$postFields = [];
$files = [];
$body = '';
$parser->on('post', function ($key, $value) use (&$postFields) {
self::extractPost($postFields, $key, $value);
});
$parser->on('file', function ($name, File $file) use (&$files) {
StreamBufferedSink::createPromise($file->getStream())->done(function ($buffer) use ($name, $file, &$files) {
$files[] = [
'name' => $name,
'file' => $file,
'buffer' => $buffer,
];
});
});
$parser->on('body', function ($rawBody) use (&$body) {
$body = $rawBody;
});
$parser->on('end', function () use ($deferred, &$postFields, &$files, &$body) {
$deferred->resolve([
'post' => $postFields,
'files' => $files,
'body' => $body,
]);
});

return $deferred->promise();
}

public static function extractPost(&$postFields, $key, $value)
{
$chunks = explode('[', $key);
if (count($chunks) == 1) {
$postFields[$key] = $value;
return;
}

$chunkKey = $chunks[0];
if (!isset($postFields[$chunkKey])) {
$postFields[$chunkKey] = [];
}

$parent = &$postFields;
for ($i = 1; $i < count($chunks); $i++) {
$previousChunkKey = $chunkKey;
if (!isset($parent[$previousChunkKey])) {
$parent[$previousChunkKey] = [];
}
$parent = &$parent[$previousChunkKey];
$chunkKey = $chunks[$i];

if ($chunkKey == ']') {
$parent[] = $value;
return;
}

$chunkKey = rtrim($chunkKey, ']');
if ($i == count($chunks) - 1) {
$parent[$chunkKey] = $value;
return;
}
}
}
}
39 changes: 39 additions & 0 deletions src/StreamingBodyParser/FilesBufferedSink.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace React\Http\StreamingBodyParser;

use React\Http\File;
use React\Promise;
use React\Stream\BufferedSink as StreamBufferedSink;

class FilesBufferedSink
{
/**
* @param ParserInterface $parser
* @return Promise\PromiseInterface
*/
public static function createPromise(ParserInterface $parser)
{
if ($parser instanceof NoBodyParser) {
return Promise\resolve([]);
}

$deferred = new Promise\Deferred();
$files = [];

$parser->on('file', function ($name, File $file) use (&$files) {
StreamBufferedSink::createPromise($file->getStream())->done(function ($buffer) use ($name, $file, &$files) {
$files[] = [
'name' => $name,
'file' => $file,
'buffer' => $buffer,
];
});
});
$parser->on('end', function () use ($deferred, &$files) {
$deferred->resolve($files);
});

return $deferred->promise();
}
}
66 changes: 66 additions & 0 deletions src/StreamingBodyParser/PostBufferedSink.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace React\Http\StreamingBodyParser;

use React\Promise;

class PostBufferedSink
{
/**
* @param ParserInterface $parser
* @return Promise\PromiseInterface
*/
public static function createPromise(ParserInterface $parser)
{
if ($parser instanceof NoBodyParser) {
return Promise\resolve([]);
}

$deferred = new Promise\Deferred();
$postFields = [];

$parser->on('post', function ($key, $value) use (&$postFields) {
self::extractPost($postFields, $key, $value);
});
$parser->on('end', function () use ($deferred, &$postFields) {
$deferred->resolve($postFields);
});

return $deferred->promise();
}

public static function extractPost(&$postFields, $key, $value)
{
$chunks = explode('[', $key);
if (count($chunks) == 1) {
$postFields[$key] = $value;
return;
}

$chunkKey = $chunks[0];
if (!isset($postFields[$chunkKey])) {
$postFields[$chunkKey] = [];
}

$parent = &$postFields;
for ($i = 1; $i < count($chunks); $i++) {
$previousChunkKey = $chunkKey;
if (!isset($parent[$previousChunkKey])) {
$parent[$previousChunkKey] = [];
}
$parent = &$parent[$previousChunkKey];
$chunkKey = $chunks[$i];

if ($chunkKey == ']') {
$parent[] = $value;
return;
}

$chunkKey = rtrim($chunkKey, ']');
if ($i == count($chunks) - 1) {
$parent[$chunkKey] = $value;
return;
}
}
}
}
34 changes: 34 additions & 0 deletions tests/StreamingBodyParser/BodyBufferedSinkTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace React\Tests\Http\StreamingBodyParser;

use Clue\React\Block;
use React\EventLoop\Factory;
use React\Http\StreamingBodyParser\BodyBufferedSink;
use React\Http\StreamingBodyParser\NoBodyParser;
use React\Http\Request;
use React\Tests\Http\TestCase;

class BodyBufferedSinkTest extends TestCase
{
public function testDoneParser()
{
$parser = new NoBodyParser(new Request('get', 'http://example.com'));
$deferredStream = BodyBufferedSink::createPromise($parser);
$result = Block\await($deferredStream, Factory::create(), 10);
$this->assertSame('', $result);
}

public function testDeferredStream()
{
$parser = new DummyParser(new Request('get', 'http://example.com'));
$deferredStream = BodyBufferedSink::createPromise($parser);

$parser->emit('body', ['abc']);
$parser->emit('end');

$result = Block\await($deferredStream, Factory::create(), 10);

$this->assertSame('abc', $result);
}
}
100 changes: 100 additions & 0 deletions tests/StreamingBodyParser/BufferedSinkTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

namespace React\Tests\Http\StreamingBodyParser;

use Clue\React\Block;
use React\EventLoop\Factory;
use React\Http\File;
use React\Http\StreamingBodyParser\BufferedSink;
use React\Http\StreamingBodyParser\NoBodyParser;
use React\Http\Request;
use React\Stream\ThroughStream;
use React\Tests\Http\TestCase;

class BufferedSinkTest extends TestCase
{
public function testDoneParser()
{
$parser = new NoBodyParser(new Request('get', 'http://example.com'));
$deferredStream = BufferedSink::createPromise($parser);
$result = Block\await($deferredStream, Factory::create(), 10);
$this->assertSame([
'post' => [],
'files' => [],
'body' => '',
], $result);
}

public function testDeferredStream()
{
$parser = new DummyParser(new Request('get', 'http://example.com'));
$deferredStream = BufferedSink::createPromise($parser);
$parser->emit('post', ['foo', 'bar']);
$parser->emit('post', ['array[]', 'foo']);
$parser->emit('post', ['array[]', 'bar']);
$parser->emit('post', ['dem[two]', 'bar']);
$parser->emit('post', ['dom[two][]', 'bar']);

$stream = new ThroughStream();
$file = new File('bar.ext', 'text', $stream);
$parser->emit('file', ['foo', $file]);
$stream->end('foo.bar');

$parser->emit('body', ['abc']);

$parser->emit('end');

$result = Block\await($deferredStream, Factory::create(), 10);
$this->assertSame([
'foo' => 'bar',
'array' => [
'foo',
'bar',
],
'dem' => [
'two' => 'bar',
],
'dom' => [
'two' => [
'bar',
],
],
], $result['post']);

$this->assertSame('foo', $result['files'][0]['name']);
$this->assertSame('bar.ext', $result['files'][0]['file']->getFilename());
$this->assertSame('text', $result['files'][0]['file']->getContentType());
$this->assertSame('foo.bar', $result['files'][0]['buffer']);

$this->assertSame('abc', $result['body']);
}

public function testExtractPost()
{
$postFields = [];
BufferedSink::extractPost($postFields, 'dem', 'value');
BufferedSink::extractPost($postFields, 'dom[one][two][]', 'value_a');
BufferedSink::extractPost($postFields, 'dom[one][two][]', 'value_b');
BufferedSink::extractPost($postFields, 'dam[]', 'value_a');
BufferedSink::extractPost($postFields, 'dam[]', 'value_b');
BufferedSink::extractPost($postFields, 'dum[sum]', 'value');
$this->assertSame([
'dem' => 'value',
'dom' => [
'one' => [
'two' => [
'value_a',
'value_b',
],
],
],
'dam' => [
'value_a',
'value_b',
],
'dum' => [
'sum' => 'value',
],
], $postFields);
}
}
Loading