From 5e29f519b4cef3ca4afc9a7bfa778a122bd05004 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 14 Aug 2017 14:47:57 +0200 Subject: [PATCH 01/22] Middleware --- composer.json | 4 +- examples/01-hello-world.php | 21 +- examples/02-count-visitors.php | 17 +- examples/03-client-ip.php | 21 +- examples/04-query-parameter.php | 29 +- examples/05-cookie-handling.php | 37 +- examples/06-sleep.php | 25 +- examples/07-error-handling.php | 37 +- examples/08-stream-response.php | 47 +- examples/09-stream-request.php | 55 +- examples/11-hello-world-https.php | 17 +- examples/12-middleware.php | 39 + examples/13-request-sec.php | 68 ++ examples/21-http-proxy.php | 49 +- examples/22-connect-proxy.php | 55 +- examples/31-upgrade-echo.php | 47 +- examples/32-upgrade-chat.php | 87 +-- examples/99-benchmark-download.php | 51 +- src/Middleware/Buffer.php | 68 ++ src/Middleware/Callback.php | 33 + src/Middleware/LimitHandlers.php | 51 ++ src/MiddlewareInterface.php | 20 + src/MiddlewareStack.php | 56 ++ src/MiddlewareStackInterface.php | 15 + src/PSR15RecoilMiddlewareStack.php | 65 ++ src/Server.php | 32 +- tests/FunctionalServerTest.php | 180 +++-- tests/Middleware/BufferTest.php | 83 +++ tests/Middleware/CallbackTest.php | 65 ++ tests/Middleware/ExposeRequest.php | 30 + tests/Middleware/ProcessStack.php | 30 + tests/MiddlewareStackTest.php | 80 +++ tests/ServerTest.php | 1055 +++++++++++++++++----------- 33 files changed, 1785 insertions(+), 784 deletions(-) create mode 100644 examples/12-middleware.php create mode 100644 examples/13-request-sec.php create mode 100644 src/Middleware/Buffer.php create mode 100644 src/Middleware/Callback.php create mode 100644 src/Middleware/LimitHandlers.php create mode 100644 src/MiddlewareInterface.php create mode 100644 src/MiddlewareStack.php create mode 100644 src/MiddlewareStackInterface.php create mode 100644 src/PSR15RecoilMiddlewareStack.php create mode 100644 tests/Middleware/BufferTest.php create mode 100644 tests/Middleware/CallbackTest.php create mode 100644 tests/Middleware/ExposeRequest.php create mode 100644 tests/Middleware/ProcessStack.php create mode 100644 tests/MiddlewareStackTest.php diff --git a/composer.json b/composer.json index e040c784..e4d3d117 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,8 @@ "react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5", "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.6", "react/promise": "^2.3 || ^1.2.1", - "evenement/evenement": "^2.0 || ^1.0" + "evenement/evenement": "^2.0 || ^1.0", + "react/promise-stream": "^0.1.1" }, "autoload": { "psr-4": { @@ -18,7 +19,6 @@ }, "require-dev": { "phpunit/phpunit": "^4.8.10||^5.0", - "react/promise-stream": "^0.1.1", "react/socket": "^1.0 || ^0.8 || ^0.7", "clue/block-react": "^1.1" } diff --git a/examples/01-hello-world.php b/examples/01-hello-world.php index f703a5d7..ce2f592b 100644 --- a/examples/01-hello-world.php +++ b/examples/01-hello-world.php @@ -2,6 +2,7 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; +use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; @@ -9,15 +10,17 @@ $loop = Factory::create(); -$server = new Server(function (ServerRequestInterface $request) { - return new Response( - 200, - array( - 'Content-Type' => 'text/plain' - ), - "Hello world\n" - ); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + return new Response( + 200, + array( + 'Content-Type' => 'text/plain' + ), + "Hello world\n" + ); + }) +]); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/02-count-visitors.php b/examples/02-count-visitors.php index 5a225110..da2ab6c9 100644 --- a/examples/02-count-visitors.php +++ b/examples/02-count-visitors.php @@ -2,6 +2,7 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; +use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; @@ -10,13 +11,15 @@ $loop = Factory::create(); $counter = 0; -$server = new Server(function (ServerRequestInterface $request) use (&$counter) { - return new Response( - 200, - array('Content-Type' => 'text/plain'), - "Welcome number " . ++$counter . "!\n" - ); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) use (&$counter) { + return new Response( + 200, + array('Content-Type' => 'text/plain'), + "Welcome number " . ++$counter . "!\n" + ); + }) +]); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/03-client-ip.php b/examples/03-client-ip.php index 3fbcabfd..c33c5d72 100644 --- a/examples/03-client-ip.php +++ b/examples/03-client-ip.php @@ -2,6 +2,7 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; +use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; @@ -9,15 +10,17 @@ $loop = Factory::create(); -$server = new Server(function (ServerRequestInterface $request) { - $body = "Your IP is: " . $request->getServerParams()['REMOTE_ADDR']; - - return new Response( - 200, - array('Content-Type' => 'text/plain'), - $body - ); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + $body = "Your IP is: " . $request->getServerParams()['REMOTE_ADDR']; + + return new Response( + 200, + array('Content-Type' => 'text/plain'), + $body + ); + }) +]); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/04-query-parameter.php b/examples/04-query-parameter.php index 3a60aae8..a41490d8 100644 --- a/examples/04-query-parameter.php +++ b/examples/04-query-parameter.php @@ -2,6 +2,7 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; +use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; @@ -9,22 +10,24 @@ $loop = Factory::create(); -$server = new Server(function (ServerRequestInterface $request) { - $queryParams = $request->getQueryParams(); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + $queryParams = $request->getQueryParams(); - $body = 'The query parameter "foo" is not set. Click the following link '; - $body .= 'to use query parameter in your request'; + $body = 'The query parameter "foo" is not set. Click the following link '; + $body .= 'to use query parameter in your request'; - if (isset($queryParams['foo'])) { - $body = 'The value of "foo" is: ' . htmlspecialchars($queryParams['foo']); - } + if (isset($queryParams['foo'])) { + $body = 'The value of "foo" is: ' . htmlspecialchars($queryParams['foo']); + } - return new Response( - 200, - array('Content-Type' => 'text/html'), - $body - ); -}); + return new Response( + 200, + array('Content-Type' => 'text/html'), + $body + ); + }) +]); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/05-cookie-handling.php b/examples/05-cookie-handling.php index 5441adbe..9e907d5b 100644 --- a/examples/05-cookie-handling.php +++ b/examples/05-cookie-handling.php @@ -2,6 +2,7 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; +use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; @@ -9,28 +10,30 @@ $loop = Factory::create(); -$server = new Server(function (ServerRequestInterface $request) { - $key = 'react\php'; +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + $key = 'react\php'; - if (isset($request->getCookieParams()[$key])) { - $body = "Your cookie value is: " . $request->getCookieParams()[$key]; + if (isset($request->getCookieParams()[$key])) { + $body = "Your cookie value is: " . $request->getCookieParams()[$key]; + + return new Response( + 200, + array('Content-Type' => 'text/plain'), + $body + ); + } return new Response( 200, - array('Content-Type' => 'text/plain'), - $body + array( + 'Content-Type' => 'text/plain', + 'Set-Cookie' => urlencode($key) . '=' . urlencode('test;more') + ), + "Your cookie has been set." ); - } - - return new Response( - 200, - array( - 'Content-Type' => 'text/plain', - 'Set-Cookie' => urlencode($key) . '=' . urlencode('test;more') - ), - "Your cookie has been set." - ); -}); + }) +]); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/06-sleep.php b/examples/06-sleep.php index 926aac10..51293c6e 100644 --- a/examples/06-sleep.php +++ b/examples/06-sleep.php @@ -2,6 +2,7 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; +use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; use React\Promise\Promise; @@ -10,18 +11,20 @@ $loop = Factory::create(); -$server = new Server(function (ServerRequestInterface $request) use ($loop) { - return new Promise(function ($resolve, $reject) use ($request, $loop) { - $loop->addTimer(1.5, function() use ($loop, $resolve) { - $response = new Response( - 200, - array('Content-Type' => 'text/plain'), - "Hello world" - ); - $resolve($response); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) use ($loop) { + return new Promise(function ($resolve, $reject) use ($request, $loop) { + $loop->addTimer(1.5, function() use ($loop, $resolve) { + $response = new Response( + 200, + array('Content-Type' => 'text/plain'), + "Hello world" + ); + $resolve($response); + }); }); - }); -}); + }) +]); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/07-error-handling.php b/examples/07-error-handling.php index 5dbc6955..03b3b049 100644 --- a/examples/07-error-handling.php +++ b/examples/07-error-handling.php @@ -2,6 +2,7 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; +use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; use React\Promise\Promise; @@ -11,23 +12,25 @@ $loop = Factory::create(); $count = 0; -$server = new Server(function (ServerRequestInterface $request) use (&$count) { - return new Promise(function ($resolve, $reject) use (&$count) { - $count++; - - if ($count%2 === 0) { - throw new Exception('Second call'); - } - - $response = new Response( - 200, - array('Content-Type' => 'text/plain'), - "Hello World!\n" - ); - - $resolve($response); - }); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) use (&$count) { + return new Promise(function ($resolve, $reject) use (&$count) { + $count++; + + if ($count%2 === 0) { + throw new Exception('Second call'); + } + + $response = new Response( + 200, + array('Content-Type' => 'text/plain'), + "Hello World!\n" + ); + + $resolve($response); + }); + }) +]); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/08-stream-response.php b/examples/08-stream-response.php index 399e3a77..ecc0f7f2 100644 --- a/examples/08-stream-response.php +++ b/examples/08-stream-response.php @@ -2,6 +2,7 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; +use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; use React\Stream\ThroughStream; @@ -10,28 +11,30 @@ $loop = Factory::create(); -$server = new Server($loop,function (ServerRequestInterface $request) use ($loop) { - if ($request->getMethod() !== 'GET' || $request->getUri()->getPath() !== '/') { - return new Response(404); - } - - $stream = new ThroughStream(); - - $timer = $loop->addPeriodicTimer(0.5, function () use ($stream) { - $stream->emit('data', array(microtime(true) . PHP_EOL)); - }); - - $loop->addTimer(5, function() use ($loop, $timer, $stream) { - $loop->cancelTimer($timer); - $stream->emit('end'); - }); - - return new Response( - 200, - array('Content-Type' => 'text/plain'), - $stream - ); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) use ($loop) { + if ($request->getMethod() !== 'GET' || $request->getUri()->getPath() !== '/') { + return new Response(404); + } + + $stream = new ThroughStream(); + + $timer = $loop->addPeriodicTimer(0.5, function () use ($stream) { + $stream->emit('data', array(microtime(true) . PHP_EOL)); + }); + + $loop->addTimer(5, function() use ($loop, $timer, $stream) { + $loop->cancelTimer($timer); + $stream->emit('end'); + }); + + return new Response( + 200, + array('Content-Type' => 'text/plain'), + $stream + ); + }) +]); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/09-stream-request.php b/examples/09-stream-request.php index bcf5456b..dfe4403b 100644 --- a/examples/09-stream-request.php +++ b/examples/09-stream-request.php @@ -2,6 +2,7 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; +use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; use React\Promise\Promise; @@ -10,33 +11,35 @@ $loop = Factory::create(); -$server = new Server(function (ServerRequestInterface $request) { - return new Promise(function ($resolve, $reject) use ($request) { - $contentLength = 0; - $request->getBody()->on('data', function ($data) use (&$contentLength) { - $contentLength += strlen($data); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + return new Promise(function ($resolve, $reject) use ($request) { + $contentLength = 0; + $request->getBody()->on('data', function ($data) use (&$contentLength) { + $contentLength += strlen($data); + }); + + $request->getBody()->on('end', function () use ($resolve, &$contentLength){ + $response = new Response( + 200, + array('Content-Type' => 'text/plain'), + "The length of the submitted request body is: " . $contentLength + ); + $resolve($response); + }); + + // an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event + $request->getBody()->on('error', function (\Exception $exception) use ($resolve, &$contentLength) { + $response = new Response( + 400, + array('Content-Type' => 'text/plain'), + "An error occured while reading at length: " . $contentLength + ); + $resolve($response); + }); }); - - $request->getBody()->on('end', function () use ($resolve, &$contentLength){ - $response = new Response( - 200, - array('Content-Type' => 'text/plain'), - "The length of the submitted request body is: " . $contentLength - ); - $resolve($response); - }); - - // an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event - $request->getBody()->on('error', function (\Exception $exception) use ($resolve, &$contentLength) { - $response = new Response( - 400, - array('Content-Type' => 'text/plain'), - "An error occured while reading at length: " . $contentLength - ); - $resolve($response); - }); - }); -}); + }) +]); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/11-hello-world-https.php b/examples/11-hello-world-https.php index 6610c3e0..f34b9648 100644 --- a/examples/11-hello-world-https.php +++ b/examples/11-hello-world-https.php @@ -2,6 +2,7 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; +use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; @@ -9,13 +10,15 @@ $loop = Factory::create(); -$server = new Server(function (ServerRequestInterface $request) { - return new Response( - 200, - array('Content-Type' => 'text/plain'), - "Hello world!\n" - ); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + return new Response( + 200, + array('Content-Type' => 'text/plain'), + "Hello world!\n" + ); + }) +]); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $socket = new \React\Socket\SecureServer($socket, $loop, array( diff --git a/examples/12-middleware.php b/examples/12-middleware.php new file mode 100644 index 00000000..22bc3cf9 --- /dev/null +++ b/examples/12-middleware.php @@ -0,0 +1,39 @@ +futureTick(function () use ($deferred) { + $deferred->resolve(new Response( + 200, + array( + 'Content-Type' => 'text/plain' + ), + "Hello world\n" + )); + }); + return $deferred->promise(); + }) +]); + +$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); +$server->listen($socket); + +echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL; + +$loop->run(); diff --git a/examples/13-request-sec.php b/examples/13-request-sec.php new file mode 100644 index 00000000..4c3cd2de --- /dev/null +++ b/examples/13-request-sec.php @@ -0,0 +1,68 @@ + 0, + 'responses' => 0, +); +final class Incre implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, MiddlewareStackInterface $stack) + { + global $counts, $total; + $total++; + $counts['requests']++; + return $stack->process($request)->then(function ($response) { + global $counts; + $counts['responses']++; + return $response; + }); + } +} + +$loop = Factory::create(); + +$loop->addPeriodicTimer(1, function () use (&$counts, &$total) { + echo 'Req/s: ', number_format($counts['requests']), PHP_EOL; + echo 'Resp/s: ', number_format($counts['responses']), PHP_EOL; + echo 'Total: ', number_format($total), PHP_EOL; + echo '---------------------', PHP_EOL; + $counts = array( + 'requests' => 0, + 'responses' => 0, + ); +}); +$server = new Server(array( + new Incre($counts), + new Callback(function (ServerRequestInterface $request) use ($loop) { + $deferred = new Deferred(); + $loop->addTimer(mt_rand(1, 10) / 10, function () use ($deferred) { + $deferred->resolve(new Response( + 200, + array( + 'Content-Type' => 'text/plain' + ), + "Hello world\n" + )); + }); + return $deferred->promise(); + }) +)); + +$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); +$server->listen($socket); + +echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL; + +$loop->run(); diff --git a/examples/21-http-proxy.php b/examples/21-http-proxy.php index 250cbf7a..bb337788 100644 --- a/examples/21-http-proxy.php +++ b/examples/21-http-proxy.php @@ -2,6 +2,7 @@ use Psr\Http\Message\RequestInterface; use React\EventLoop\Factory; +use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; use RingCentral\Psr7; @@ -10,32 +11,34 @@ $loop = Factory::create(); -$server = new Server(function (RequestInterface $request) { - if (strpos($request->getRequestTarget(), '://') === false) { +$server = new Server([ + new Callback(function (RequestInterface $request) { + if (strpos($request->getRequestTarget(), '://') === false) { + return new Response( + 400, + array('Content-Type' => 'text/plain'), + 'This is a plain HTTP proxy' + ); + } + + // prepare outgoing client request by updating request-target and Host header + $host = (string)$request->getUri()->withScheme('')->withPath('')->withQuery(''); + $target = (string)$request->getUri()->withScheme('')->withHost('')->withPort(null); + if ($target === '') { + $target = $request->getMethod() === 'OPTIONS' ? '*' : '/'; + } + $outgoing = $request->withRequestTarget($target)->withHeader('Host', $host); + + // pseudo code only: simply dump the outgoing request as a string + // left up as an exercise: use an HTTP client to send the outgoing request + // and forward the incoming response to the original client request return new Response( - 400, + 200, array('Content-Type' => 'text/plain'), - 'This is a plain HTTP proxy' + Psr7\str($outgoing) ); - } - - // prepare outgoing client request by updating request-target and Host header - $host = (string)$request->getUri()->withScheme('')->withPath('')->withQuery(''); - $target = (string)$request->getUri()->withScheme('')->withHost('')->withPort(null); - if ($target === '') { - $target = $request->getMethod() === 'OPTIONS' ? '*' : '/'; - } - $outgoing = $request->withRequestTarget($target)->withHeader('Host', $host); - - // pseudo code only: simply dump the outgoing request as a string - // left up as an exercise: use an HTTP client to send the outgoing request - // and forward the incoming response to the original client request - return new Response( - 200, - array('Content-Type' => 'text/plain'), - Psr7\str($outgoing) - ); -}); + }) +]); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/22-connect-proxy.php b/examples/22-connect-proxy.php index ed8e80b0..dfca55e0 100644 --- a/examples/22-connect-proxy.php +++ b/examples/22-connect-proxy.php @@ -2,44 +2,47 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; +use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; -use React\Socket\Connector; use React\Socket\ConnectionInterface; +use React\Socket\Connector; require __DIR__ . '/../vendor/autoload.php'; $loop = Factory::create(); $connector = new Connector($loop); -$server = new Server(function (ServerRequestInterface $request) use ($connector) { - if ($request->getMethod() !== 'CONNECT') { - return new Response( - 405, - array('Content-Type' => 'text/plain', 'Allow' => 'CONNECT'), - 'This is a HTTP CONNECT (secure HTTPS) proxy' - ); - } - - // try to connect to given target host - return $connector->connect($request->getRequestTarget())->then( - function (ConnectionInterface $remote) { - // connection established => forward data +$server = new Server([ + new Callback(function (ServerRequestInterface $request) use ($connector) { + if ($request->getMethod() !== 'CONNECT') { return new Response( - 200, - array(), - $remote - ); - }, - function ($e) { - return new Response( - 502, - array('Content-Type' => 'text/plain'), - 'Unable to connect: ' . $e->getMessage() + 405, + array('Content-Type' => 'text/plain', 'Allow' => 'CONNECT'), + 'This is a HTTP CONNECT (secure HTTPS) proxy' ); } - ); -}); + + // try to connect to given target host + return $connector->connect($request->getRequestTarget())->then( + function (ConnectionInterface $remote) { + // connection established => forward data + return new Response( + 200, + array(), + $remote + ); + }, + function ($e) { + return new Response( + 502, + array('Content-Type' => 'text/plain'), + 'Unable to connect: ' . $e->getMessage() + ); + } + ); + }) +]); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/31-upgrade-echo.php b/examples/31-upgrade-echo.php index b098ef03..2fbd2803 100644 --- a/examples/31-upgrade-echo.php +++ b/examples/31-upgrade-echo.php @@ -19,6 +19,7 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; +use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; use React\Stream\ThroughStream; @@ -27,28 +28,30 @@ $loop = Factory::create(); -$server = new Server(function (ServerRequestInterface $request) use ($loop) { - if ($request->getHeaderLine('Upgrade') !== 'echo' || $request->getProtocolVersion() === '1.0') { - return new Response(426, array('Upgrade' => 'echo'), '"Upgrade: echo" required'); - } - - // simply return a duplex ThroughStream here - // it will simply emit any data that is sent to it - // this means that any Upgraded data will simply be sent back to the client - $stream = new ThroughStream(); - - $loop->addTimer(0, function () use ($stream) { - $stream->write("Hello! Anything you send will be piped back." . PHP_EOL); - }); - - return new Response( - 101, - array( - 'Upgrade' => 'echo' - ), - $stream - ); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) use ($loop) { + if ($request->getHeaderLine('Upgrade') !== 'echo' || $request->getProtocolVersion() === '1.0') { + return new Response(426, array('Upgrade' => 'echo'), '"Upgrade: echo" required'); + } + + // simply return a duplex ThroughStream here + // it will simply emit any data that is sent to it + // this means that any Upgraded data will simply be sent back to the client + $stream = new ThroughStream(); + + $loop->addTimer(0, function () use ($stream) { + $stream->write("Hello! Anything you send will be piped back." . PHP_EOL); + }); + + return new Response( + 101, + array( + 'Upgrade' => 'echo' + ), + $stream + ); + }) +]); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/32-upgrade-chat.php b/examples/32-upgrade-chat.php index 49cb0305..be9b1d07 100644 --- a/examples/32-upgrade-chat.php +++ b/examples/32-upgrade-chat.php @@ -21,6 +21,7 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; +use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; use React\Stream\CompositeStream; @@ -35,48 +36,50 @@ // this means that any Upgraded data will simply be sent back to the client $chat = new ThroughStream(); -$server = new Server(function (ServerRequestInterface $request) use ($loop, $chat) { - if ($request->getHeaderLine('Upgrade') !== 'chat' || $request->getProtocolVersion() === '1.0') { - return new Response(426, array('Upgrade' => 'chat'), '"Upgrade: chat" required'); - } - - // user stream forwards chat data and accepts incoming data - $out = $chat->pipe(new ThroughStream()); - $in = new ThroughStream(); - $stream = new CompositeStream( - $out, - $in - ); - - // assign some name for this new connection - $username = 'user' . mt_rand(); - - // send anything that is received to the whole channel - $in->on('data', function ($data) use ($username, $chat) { - $data = trim(preg_replace('/[^\w\d \.\,\-\!\?]/u', '', $data)); - - $chat->write($username . ': ' . $data . PHP_EOL); - }); - - // say hello to new user - $loop->addTimer(0, function () use ($chat, $username, $out) { - $out->write('Welcome to this chat example, ' . $username . '!' . PHP_EOL); - $chat->write($username . ' joined' . PHP_EOL); - }); - - // send goodbye to channel once connection closes - $stream->on('close', function () use ($username, $chat) { - $chat->write($username . ' left' . PHP_EOL); - }); - - return new Response( - 101, - array( - 'Upgrade' => 'chat' - ), - $stream - ); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) use ($loop, $chat) { + if ($request->getHeaderLine('Upgrade') !== 'chat' || $request->getProtocolVersion() === '1.0') { + return new Response(426, array('Upgrade' => 'chat'), '"Upgrade: chat" required'); + } + + // user stream forwards chat data and accepts incoming data + $out = $chat->pipe(new ThroughStream()); + $in = new ThroughStream(); + $stream = new CompositeStream( + $out, + $in + ); + + // assign some name for this new connection + $username = 'user' . mt_rand(); + + // send anything that is received to the whole channel + $in->on('data', function ($data) use ($username, $chat) { + $data = trim(preg_replace('/[^\w\d \.\,\-\!\?]/u', '', $data)); + + $chat->write($username . ': ' . $data . PHP_EOL); + }); + + // say hello to new user + $loop->addTimer(0, function () use ($chat, $username, $out) { + $out->write('Welcome to this chat example, ' . $username . '!' . PHP_EOL); + $chat->write($username . ' joined' . PHP_EOL); + }); + + // send goodbye to channel once connection closes + $stream->on('close', function () use ($username, $chat) { + $chat->write($username . ' left' . PHP_EOL); + }); + + return new Response( + 101, + array( + 'Upgrade' => 'chat' + ), + $stream + ); + }) +]); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/99-benchmark-download.php b/examples/99-benchmark-download.php index a8a6e03a..ffac4697 100644 --- a/examples/99-benchmark-download.php +++ b/examples/99-benchmark-download.php @@ -9,6 +9,7 @@ use Evenement\EventEmitter; use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; +use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; use React\Stream\ReadableStreamInterface; @@ -86,32 +87,34 @@ public function getSize() } } -$server = new Server(function (ServerRequestInterface $request) use ($loop) { - switch ($request->getUri()->getPath()) { - case '/': - return new Response( - 200, - array('Content-Type' => 'text/html'), - '1g.bin
10g.bin' - ); - case '/1g.bin': - $stream = new ChunkRepeater(str_repeat('.', 1000000), 1000); - break; - case '/10g.bin': - $stream = new ChunkRepeater(str_repeat('.', 1000000), 10000); - break; - default: - return new Response(404); - } +$server = new Server([ + new Callback(function (ServerRequestInterface $request) use ($loop) { + switch ($request->getUri()->getPath()) { + case '/': + return new Response( + 200, + array('Content-Type' => 'text/html'), + '1g.bin
10g.bin' + ); + case '/1g.bin': + $stream = new ChunkRepeater(str_repeat('.', 1000000), 1000); + break; + case '/10g.bin': + $stream = new ChunkRepeater(str_repeat('.', 1000000), 10000); + break; + default: + return new Response(404); + } - $loop->addTimer(0, array($stream, 'resume')); + $loop->addTimer(0, array($stream, 'resume')); - return new Response( - 200, - array('Content-Type' => 'application/octet-data', 'Content-Length' => $stream->getSize()), - $stream - ); -}); + return new Response( + 200, + array('Content-Type' => 'application/octet-data', 'Content-Length' => $stream->getSize()), + $stream + ); + }) +]); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/src/Middleware/Buffer.php b/src/Middleware/Buffer.php new file mode 100644 index 00000000..ed657f31 --- /dev/null +++ b/src/Middleware/Buffer.php @@ -0,0 +1,68 @@ +iniMaxPostSize(); + } + + $this->sizeLimit = $sizeLimit; + } + + public function process(ServerRequestInterface $request, MiddlewareStackInterface $stack) + { + $size = $request->getBody()->getSize(); + + if ($size === null) { + return new Response(411, array('Content-Type' => 'text/plain'), 'No Content-Length header given'); + } + + if ($size > $this->sizeLimit) { + return new Response(413, array('Content-Type' => 'text/plain'), 'Request body exceeds allowed limit'); + } + + $body = $request->getBody(); + if (!$body instanceof ReadableStreamInterface) { + return $stack->process($request); + } + + return Stream\buffer($body)->then(function ($buffer) use ($request, $stack) { + $stream = new BufferStream(strlen($buffer)); + $stream->write($buffer); + $request = $request->withBody($stream); + + return $stack->process($request); + }); + } + + private function iniMaxPostSize() + { + $size = ini_get('post_max_size'); + $suffix = strtoupper(substr($size, -1)); + if ($suffix === 'K') { + return substr($size, 0, -1) * 1024; + } + if ($suffix === 'M') { + return substr($size, 0, -1) * 1024 * 1024; + } + if ($suffix === 'G') { + return substr($size, 0, -1) * 1024 * 1024 * 1024; + } + + return $size; + } +} diff --git a/src/Middleware/Callback.php b/src/Middleware/Callback.php new file mode 100644 index 00000000..51a408b4 --- /dev/null +++ b/src/Middleware/Callback.php @@ -0,0 +1,33 @@ +callback = $callback; + } + + public function process(ServerRequestInterface $request, MiddlewareStackInterface $stack) + { + $callback = $this->callback; + + return Promise\resolve($callback($request))->then(function ($response) use ($stack) { + if ($response instanceof ResponseInterface) { + return $response; + } + + // Assuming since $response isn't a response it is a request + return $stack->process($response); + }); + } +} diff --git a/src/Middleware/LimitHandlers.php b/src/Middleware/LimitHandlers.php new file mode 100644 index 00000000..784eb6f2 --- /dev/null +++ b/src/Middleware/LimitHandlers.php @@ -0,0 +1,51 @@ +limit = $limit; + $this->queued = new \SplQueue(); + } + + public function process(ServerRequestInterface $request, MiddlewareStackInterface $stack) + { + $deferred = new Deferred(); + $this->queued->enqueue($deferred); + + $this->processQueue(); + + return $deferred->promise()->then(function () use ($request, $stack) { + $this->pending++; + return $stack->process($request); + })->then(function ($response) { + $this->pending--; + $this->processQueue(); + return $response; + }); + } + + private function processQueue() + { + if ($this->pending >= $this->limit) { + return; + } + + if ($this->queued->count() === 0) { + return; + } + + $this->queued->dequeue()->resolve(); + } +} diff --git a/src/MiddlewareInterface.php b/src/MiddlewareInterface.php new file mode 100644 index 00000000..787b0e1c --- /dev/null +++ b/src/MiddlewareInterface.php @@ -0,0 +1,20 @@ + + */ + public function process(ServerRequestInterface $request, MiddlewareStackInterface $stack); +} diff --git a/src/MiddlewareStack.php b/src/MiddlewareStack.php new file mode 100644 index 00000000..4fda2288 --- /dev/null +++ b/src/MiddlewareStack.php @@ -0,0 +1,56 @@ +defaultResponse = $response; + $this->middlewares = $middlewares; + } + + /** + * @param ServerRequestInterface $request + * @return PromiseInterface + */ + public function process(ServerRequestInterface $request) + { + if (count($this->middlewares) === 0) { + return Promise\resolve($this->defaultResponse); + } + + $middlewares = $this->middlewares; + $middleware = array_shift($middlewares); + + return Promise\resolve( + $middleware->process( + $request, + new self( + $this->defaultResponse, + $middlewares + ) + ) + ); + } +} diff --git a/src/MiddlewareStackInterface.php b/src/MiddlewareStackInterface.php new file mode 100644 index 00000000..62947aa2 --- /dev/null +++ b/src/MiddlewareStackInterface.php @@ -0,0 +1,15 @@ + + */ + public function process(ServerRequestInterface $request); +} diff --git a/src/PSR15RecoilMiddlewareStack.php b/src/PSR15RecoilMiddlewareStack.php new file mode 100644 index 00000000..e18c8d41 --- /dev/null +++ b/src/PSR15RecoilMiddlewareStack.php @@ -0,0 +1,65 @@ +kernel = $kernel; + $this->defaultResponse = $response; + $this->middlewares = $middlewares; + } + + public function process(ServerRequestInterface $request, DelegateInterface $stack) + { + if (count($this->middlewares) === 0) { + return Promise\resolve($this->defaultResponse); + } + + $middlewares = $this->middlewares; + $middleware = array_shift($middlewares); + + return new Promise\Promise(function ($resolve, $reject) { + $this->kernel->execute(function () use ($resolve, $reject) { + $middleware->process( + $request, + new self( + $this->defaultResponse, + $middlewares + ) + ); + }); + }); + } +} diff --git a/src/Server.php b/src/Server.php index 8f80fa71..9eb9d7e0 100644 --- a/src/Server.php +++ b/src/Server.php @@ -3,11 +3,14 @@ namespace React\Http; use Evenement\EventEmitter; +use Interop\Http\ServerMiddleware\DelegateInterface; +use Interop\Http\ServerMiddleware\MiddlewareInterface; +use React\Http\Middleware\Callback; use React\Socket\ServerInterface; use React\Socket\ConnectionInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use React\Promise\Promise; +use React\Promise; use RingCentral\Psr7 as Psr7Implementation; use Psr\Http\Message\ServerRequestInterface; use React\Promise\CancellablePromiseInterface; @@ -75,7 +78,7 @@ */ class Server extends EventEmitter { - private $callback; + private $middlewareStack; /** * Creates an HTTP server that invokes the given callback for each incoming HTTP request @@ -85,16 +88,25 @@ class Server extends EventEmitter * connections in order to then parse incoming data as HTTP. * See also [listen()](#listen) for more details. * - * @param callable $callback + * @param MiddlewareInterface[] $middlewares * @see self::listen() */ - public function __construct($callback) + public function __construct($middlewares) { - if (!is_callable($callback)) { - throw new \InvalidArgumentException(); + if (is_array($middlewares)) { + $this->middlewareStack = new MiddlewareStack( + new Response(404), + $middlewares + ); + return; + } + + if ($middlewares instanceof MiddlewareStack) { + $this->middlewareStack = $middlewares; + return; } - $this->callback = $callback; + throw new \InvalidArgumentException(); } /** @@ -227,10 +239,10 @@ public function handleRequest(ConnectionInterface $conn, ServerRequestInterface $conn->write("HTTP/1.1 100 Continue\r\n\r\n"); } - $callback = $this->callback; + $middlewareStack = $this->middlewareStack; $cancel = null; - $promise = new Promise(function ($resolve, $reject) use ($callback, $request, &$cancel) { - $cancel = $callback($request); + $promise = new Promise\Promise(function ($resolve, $reject) use ($middlewareStack, $request, &$cancel) { + $cancel = $middlewareStack->process($request); $resolve($cancel); }); diff --git a/tests/FunctionalServerTest.php b/tests/FunctionalServerTest.php index 06f06db9..c049f69c 100644 --- a/tests/FunctionalServerTest.php +++ b/tests/FunctionalServerTest.php @@ -2,6 +2,8 @@ namespace React\Tests\Http; +use React\Http\Middleware\Buffer; +use React\Http\Middleware\Callback; use React\Socket\Server as Socket; use React\EventLoop\Factory; use React\Http\Server; @@ -24,9 +26,11 @@ public function testPlainHttpOnRandomPort() $loop = Factory::create(); $connector = new Connector($loop); - $server = new Server(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }); + $server = new Server(array( + new Callback(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }) + )); $socket = new Socket(0, $loop); $server->listen($socket); @@ -50,9 +54,11 @@ public function testPlainHttpOnRandomPortWithoutHostHeaderUsesSocketUri() $loop = Factory::create(); $connector = new Connector($loop); - $server = new Server(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }); + $server = new Server(array( + new Callback(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }) + )); $socket = new Socket(0, $loop); $server->listen($socket); @@ -76,9 +82,11 @@ public function testPlainHttpOnRandomPortWithOtherHostHeaderTakesPrecedence() $loop = Factory::create(); $connector = new Connector($loop); - $server = new Server(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }); + $server = new Server(array( + new Callback(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }) + )); $socket = new Socket(0, $loop); $server->listen($socket); @@ -108,9 +116,11 @@ public function testSecureHttpsOnRandomPort() 'tls' => array('verify_peer' => false) )); - $server = new Server(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }); + $server = new Server(array( + new Callback(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }) + )); $socket = new Socket(0, $loop); $socket = new SecureServer($socket, $loop, array( @@ -143,9 +153,11 @@ public function testSecureHttpsOnRandomPortWithoutHostHeaderUsesSocketUri() 'tls' => array('verify_peer' => false) )); - $server = new Server(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }); + $server = new Server(array( + new Callback(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }) + )); $socket = new Socket(0, $loop); $socket = new SecureServer($socket, $loop, array( @@ -177,9 +189,11 @@ public function testPlainHttpOnStandardPortReturnsUriWithNoPort() } $connector = new Connector($loop); - $server = new Server(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }); + $server = new Server(array( + new Callback(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }) + )); $server->listen($socket); @@ -207,9 +221,11 @@ public function testPlainHttpOnStandardPortWithoutHostHeaderReturnsUriWithNoPort } $connector = new Connector($loop); - $server = new Server(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }); + $server = new Server(array( + new Callback(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }) + )); $server->listen($socket); @@ -246,9 +262,11 @@ public function testSecureHttpsOnStandardPortReturnsUriWithNoPort() 'tls' => array('verify_peer' => false) )); - $server = new Server(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }); + $server = new Server(array( + new Callback(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }) + )); $server->listen($socket); @@ -285,9 +303,11 @@ public function testSecureHttpsOnStandardPortWithoutHostHeaderUsesSocketUri() 'tls' => array('verify_peer' => false) )); - $server = new Server(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }); + $server = new Server(array( + new Callback(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }) + )); $server->listen($socket); @@ -315,9 +335,11 @@ public function testPlainHttpOnHttpsStandardPortReturnsUriWithPort() } $connector = new Connector($loop); - $server = new Server(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }); + $server = new Server(array( + new Callback(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }) + )); $server->listen($socket); @@ -354,9 +376,11 @@ public function testSecureHttpsOnHttpStandardPortReturnsUriWithPort() 'tls' => array('verify_peer' => false) )); - $server = new Server(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri() . 'x' . $request->getHeaderLine('Host')); - }); + $server = new Server(array( + new Callback(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri() . 'x' . $request->getHeaderLine('Host')); + }) + )); $server->listen($socket); @@ -382,9 +406,11 @@ public function testClosedStreamFromRequestHandlerWillSendEmptyBody() $stream = new ThroughStream(); $stream->close(); - $server = new Server(function (RequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }); + $server = new Server(array( + new Callback(function (RequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }) + )); $socket = new Socket(0, $loop); $server->listen($socket); @@ -411,9 +437,11 @@ public function testStreamFromRequestHandlerWillBeClosedIfConnectionClosesWhileS $stream = new ThroughStream(); $stream->on('close', $this->expectCallableOnce()); - $server = new Server(function (RequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }); + $server = new Server(array( + new Callback(function (RequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }), + )); $socket = new Socket(0, $loop); $server->listen($socket); @@ -444,9 +472,11 @@ public function testStreamFromRequestHandlerWillBeClosedIfConnectionClosesButWil $stream = new ThroughStream(); $stream->on('close', $this->expectCallableOnce()); - $server = new Server(function (RequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }); + $server = new Server(array( + new Callback(function (RequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }) + )); $socket = new Socket(0, $loop); $server->listen($socket); @@ -479,15 +509,17 @@ public function testUpgradeWithThroughStreamReturnsDataAsGiven() $loop = Factory::create(); $connector = new Connector($loop); - $server = new Server(function (RequestInterface $request) use ($loop) { - $stream = new ThroughStream(); + $server = new Server(array( + new Callback(function (RequestInterface $request) use ($loop) { + $stream = new ThroughStream(); - $loop->addTimer(0.1, function () use ($stream) { - $stream->end(); - }); + $loop->addTimer(0.1, function () use ($stream) { + $stream->end(); + }); - return new Response(101, array('Upgrade' => 'echo'), $stream); - }); + return new Response(101, array('Upgrade' => 'echo'), $stream); + }) + )); $socket = new Socket(0, $loop); $server->listen($socket); @@ -516,15 +548,17 @@ public function testConnectWithThroughStreamReturnsDataAsGiven() $loop = Factory::create(); $connector = new Connector($loop); - $server = new Server(function (RequestInterface $request) use ($loop) { - $stream = new ThroughStream(); + $server = new Server(array( + new Callback(function (RequestInterface $request) use ($loop) { + $stream = new ThroughStream(); - $loop->addTimer(0.1, function () use ($stream) { - $stream->end(); - }); + $loop->addTimer(0.1, function () use ($stream) { + $stream->end(); + }); - return new Response(200, array(), $stream); - }); + return new Response(200, array(), $stream); + }) + )); $socket = new Socket(0, $loop); $server->listen($socket); @@ -553,19 +587,21 @@ public function testConnectWithThroughStreamReturnedFromPromiseReturnsDataAsGive $loop = Factory::create(); $connector = new Connector($loop); - $server = new Server(function (RequestInterface $request) use ($loop) { - $stream = new ThroughStream(); + $server = new Server(array( + new Callback(function (RequestInterface $request) use ($loop) { + $stream = new ThroughStream(); - $loop->addTimer(0.1, function () use ($stream) { - $stream->end(); - }); + $loop->addTimer(0.1, function () use ($stream) { + $stream->end(); + }); - return new Promise(function ($resolve) use ($loop, $stream) { - $loop->addTimer(0.001, function () use ($resolve, $stream) { - $resolve(new Response(200, array(), $stream)); + return new Promise(function ($resolve) use ($loop, $stream) { + $loop->addTimer(0.001, function () use ($resolve, $stream) { + $resolve(new Response(200, array(), $stream)); + }); }); - }); - }); + }) + )); $socket = new Socket(0, $loop); $server->listen($socket); @@ -594,12 +630,14 @@ public function testConnectWithClosedThroughStreamReturnsNoData() $loop = Factory::create(); $connector = new Connector($loop); - $server = new Server(function (RequestInterface $request) { - $stream = new ThroughStream(); - $stream->close(); + $server = new Server(array( + new Callback(function (RequestInterface $request) { + $stream = new ThroughStream(); + $stream->close(); - return new Response(200, array(), $stream); - }); + return new Response(200, array(), $stream); + }) + )); $socket = new Socket(0, $loop); $server->listen($socket); diff --git a/tests/Middleware/BufferTest.php b/tests/Middleware/BufferTest.php new file mode 100644 index 00000000..c9bff90b --- /dev/null +++ b/tests/Middleware/BufferTest.php @@ -0,0 +1,83 @@ +write($body); + $serverRequest = new ServerRequest( + 'GET', + 'https://example.com/', + array(), + $stream + ); + + $exposeRequest = new ExposeRequest(); + + $response = new Response(); + $stack = new MiddlewareStack($response, [$exposeRequest]); + + $buffer = new Buffer(); + $buffer->process($serverRequest, $stack); + + $exposedRequest = $exposeRequest->getRequest(); + $this->assertSame($body, $exposedRequest->getBody()->getContents()); + } + + public function testToLargeBody() + { + $size = $this->iniMaxPostSize() + 1; + $stream = new BufferStream($size); + $stream->write(str_repeat('x', $size)); + $serverRequest = new ServerRequest( + 'GET', + 'https://example.com/', + array(), + $stream + ); + + $stack = $this + ->getMockBuilder('React\Http\MiddlewareStackInterface') + ->getMock(); + $stack + ->expects($this->never()) + ->method('process') + ->with($serverRequest); + + $buffer = new Buffer(); + $response = $buffer->process($serverRequest, $stack); + + $this->assertInstanceOf('React\Http\Response', $response); + $this->assertSame(413, $response->getStatusCode()); + $this->assertSame('Request body exceeds allowed limit', (string)$response->getBody()); + } + + private function iniMaxPostSize() + { + $size = ini_get('post_max_size'); + $suffix = strtoupper(substr($size, -1)); + if ($suffix === 'K') { + return substr($size, 0, -1) * 1024; + } + if ($suffix === 'M') { + return substr($size, 0, -1) * 1024 * 1024; + } + if ($suffix === 'G') { + return substr($size, 0, -1) * 1024 * 1024 * 1024; + } + + return $size; + } +} diff --git a/tests/Middleware/CallbackTest.php b/tests/Middleware/CallbackTest.php new file mode 100644 index 00000000..bc7645c4 --- /dev/null +++ b/tests/Middleware/CallbackTest.php @@ -0,0 +1,65 @@ +getMockBuilder('Psr\Http\Message\ServerRequestInterface') + ->getMock(); + $called = false; + $callback = new Callback(function () use (&$called, $request) { + $called = true; + return $request; + }); + $stack = $this + ->getMockBuilder('React\Http\MiddlewareStackInterface') + ->getMock(); + + $stack + ->expects($this->once()) + ->method('process') + ->with($request) + ->willReturn($request); + + $result = Block\await($callback->process($request, $stack), Factory::create()); + + $this->assertSame($request, $result); + $this->assertTrue($called); + } + + public function testResponse() + { + $request = $this + ->getMockBuilder('Psr\Http\Message\ServerRequestInterface') + ->getMock(); + $response = $this + ->getMockBuilder('Psr\Http\Message\ResponseInterface') + ->getMock(); + $called = false; + $callback = new Callback(function () use (&$called, $response) { + $called = true; + return $response; + }); + $stack = $this + ->getMockBuilder('React\Http\MiddlewareStackInterface') + ->getMock(); + + $stack + ->expects($this->never()) + ->method('process') + ->with($request); + + $result = Block\await($callback->process($request, $stack), Factory::create()); + + $this->assertSame($response, $result); + $this->assertTrue($called); + } +} diff --git a/tests/Middleware/ExposeRequest.php b/tests/Middleware/ExposeRequest.php new file mode 100644 index 00000000..a1e20c17 --- /dev/null +++ b/tests/Middleware/ExposeRequest.php @@ -0,0 +1,30 @@ +request = $request; + return Promise\resolve($stack->process($request)); + } + + /** + * @return ServerRequestInterface + */ + public function getRequest() + { + return $this->request; + } +} diff --git a/tests/Middleware/ProcessStack.php b/tests/Middleware/ProcessStack.php new file mode 100644 index 00000000..7dd0c60f --- /dev/null +++ b/tests/Middleware/ProcessStack.php @@ -0,0 +1,30 @@ +callCount++; + return Promise\resolve($stack->process($request)); + } + + /** + * @return int + */ + public function getCallCount() + { + return $this->callCount; + } +} diff --git a/tests/MiddlewareStackTest.php b/tests/MiddlewareStackTest.php new file mode 100644 index 00000000..6848a7e3 --- /dev/null +++ b/tests/MiddlewareStackTest.php @@ -0,0 +1,80 @@ +process($request), Factory::create()); + $this->assertSame($defaultResponse, $result); + } + + public function provideProcessStackMiddlewares() + { + $processStackA = new ProcessStack(); + $processStackB = new ProcessStack(); + $processStackC = new ProcessStack(); + $processStackD = new ProcessStack(); + return array( + array( + array( + $processStackA, + ), + 1, + ), + array( + array( + $processStackB, + $processStackB, + ), + 2, + ), + array( + array( + $processStackC, + $processStackC, + $processStackC, + ), + 3, + ), + array( + array( + $processStackD, + $processStackD, + $processStackD, + $processStackD, + ), + 4, + ), + ); + } + + /** + * @dataProvider provideProcessStackMiddlewares + */ + public function testProcessStack(array $middlewares, $expectedCallCount) + { + $request = new ServerRequest('GET', 'https://example.com/'); + $defaultResponse = new Response(404); + $middlewareStack = new MiddlewareStack($defaultResponse, $middlewares); + + $result = Block\await($middlewareStack->process($request), Factory::create()); + $this->assertSame($defaultResponse, $result); + foreach ($middlewares as $middleware) { + $this->assertSame($expectedCallCount, $middleware->getCallCount()); + } + } +} diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 88d5e54b..7fb14ae9 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -2,6 +2,7 @@ namespace React\Tests\Http; +use React\Http\Middleware\Callback; use React\Http\Server; use Psr\Http\Message\ServerRequestInterface; use React\Http\Response; @@ -41,7 +42,9 @@ public function setUp() public function testRequestEventWillNotBeEmittedForIncompleteHeaders() { - $server = new Server($this->expectCallableNever()); + $server = new Server(array( + new Callback($this->expectCallableNever()) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -53,9 +56,11 @@ public function testRequestEventWillNotBeEmittedForIncompleteHeaders() public function testRequestEventIsEmitted() { - $server = new Server(function (ServerRequestInterface $request) { - return \React\Promise\resolve(new Response()); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return \React\Promise\resolve(new Response()); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -68,12 +73,14 @@ public function testRequestEvent() { $i = 0; $requestAssertion = null; - $server = new Server(function (ServerRequestInterface $request) use (&$i, &$requestAssertion) { - $i++; - $requestAssertion = $request; + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$i, &$requestAssertion) { + $i++; + $requestAssertion = $request; - return \React\Promise\resolve(new Response()); - }); + return \React\Promise\resolve(new Response()); + }) + )); $this->connection ->expects($this->any()) @@ -102,10 +109,12 @@ public function testRequestEvent() public function testRequestGetWithHostAndCustomPort() { $requestAssertion = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -125,10 +134,12 @@ public function testRequestGetWithHostAndCustomPort() public function testRequestGetWithHostAndHttpsPort() { $requestAssertion = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -148,10 +159,12 @@ public function testRequestGetWithHostAndHttpsPort() public function testRequestGetWithHostAndDefaultPortWillBeIgnored() { $requestAssertion = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -171,10 +184,12 @@ public function testRequestGetWithHostAndDefaultPortWillBeIgnored() public function testRequestOptionsAsterisk() { $requestAssertion = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -192,7 +207,9 @@ public function testRequestOptionsAsterisk() public function testRequestNonOptionsWithAsteriskRequestTargetWillReject() { - $server = new Server($this->expectCallableNever()); + $server = new Server(array( + new Callback($this->expectCallableNever()) + )); $server->on('error', $this->expectCallableOnce()); $server->listen($this->socket); @@ -205,10 +222,12 @@ public function testRequestNonOptionsWithAsteriskRequestTargetWillReject() public function testRequestConnectAuthorityForm() { $requestAssertion = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -228,10 +247,12 @@ public function testRequestConnectAuthorityForm() public function testRequestConnectWithoutHostWillBeAdded() { $requestAssertion = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -251,10 +272,12 @@ public function testRequestConnectWithoutHostWillBeAdded() public function testRequestConnectAuthorityFormWithDefaultPortWillBeIgnored() { $requestAssertion = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -274,10 +297,12 @@ public function testRequestConnectAuthorityFormWithDefaultPortWillBeIgnored() public function testRequestConnectAuthorityFormNonMatchingHostWillBeOverwritten() { $requestAssertion = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -296,7 +321,9 @@ public function testRequestConnectAuthorityFormNonMatchingHostWillBeOverwritten( public function testRequestConnectOriginFormRequestTargetWillReject() { - $server = new Server($this->expectCallableNever()); + $server = new Server(array( + new Callback($this->expectCallableNever()) + )); $server->on('error', $this->expectCallableOnce()); $server->listen($this->socket); @@ -308,7 +335,9 @@ public function testRequestConnectOriginFormRequestTargetWillReject() public function testRequestNonConnectWithAuthorityRequestTargetWillReject() { - $server = new Server($this->expectCallableNever()); + $server = new Server(array( + new Callback($this->expectCallableNever()) + )); $server->on('error', $this->expectCallableOnce()); $server->listen($this->socket); @@ -322,10 +351,12 @@ public function testRequestWithoutHostEventUsesSocketAddress() { $requestAssertion = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }) + )); $this->connection ->expects($this->any()) @@ -349,10 +380,12 @@ public function testRequestAbsoluteEvent() { $requestAssertion = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -372,10 +405,12 @@ public function testRequestAbsoluteAddsMissingHostEvent() { $requestAssertion = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }) + )); $server->on('error', 'printf'); $server->listen($this->socket); @@ -396,10 +431,12 @@ public function testRequestAbsoluteNonMatchingHostWillBeOverwritten() { $requestAssertion = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -419,10 +456,12 @@ public function testRequestOptionsAsteriskEvent() { $requestAssertion = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -442,10 +481,12 @@ public function testRequestOptionsAbsoluteEvent() { $requestAssertion = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -463,10 +504,12 @@ public function testRequestOptionsAbsoluteEvent() public function testRequestPauseWillbeForwardedToConnection() { - $server = new Server(function (ServerRequestInterface $request) { - $request->getBody()->pause(); - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + $request->getBody()->pause(); + return new Response(); + }) + )); $this->connection->expects($this->once())->method('pause'); @@ -484,10 +527,12 @@ public function testRequestPauseWillbeForwardedToConnection() public function testRequestResumeWillbeForwardedToConnection() { - $server = new Server(function (ServerRequestInterface $request) { - $request->getBody()->resume(); - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + $request->getBody()->resume(); + return new Response(); + }) + )); $this->connection->expects($this->once())->method('resume'); @@ -500,10 +545,12 @@ public function testRequestResumeWillbeForwardedToConnection() public function testRequestCloseWillPauseConnection() { - $server = new Server(function (ServerRequestInterface $request) { - $request->getBody()->close(); - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + $request->getBody()->close(); + return new Response(); + }) + )); $this->connection->expects($this->once())->method('pause'); @@ -516,12 +563,14 @@ public function testRequestCloseWillPauseConnection() public function testRequestPauseAfterCloseWillNotBeForwarded() { - $server = new Server(function (ServerRequestInterface $request) { - $request->getBody()->close(); - $request->getBody()->pause();# + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + $request->getBody()->close(); + $request->getBody()->pause();# - return new Response(); - }); + return new Response(); + }) + )); $this->connection->expects($this->once())->method('pause'); @@ -534,12 +583,14 @@ public function testRequestPauseAfterCloseWillNotBeForwarded() public function testRequestResumeAfterCloseWillNotBeForwarded() { - $server = new Server(function (ServerRequestInterface $request) { - $request->getBody()->close(); - $request->getBody()->resume(); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + $request->getBody()->close(); + $request->getBody()->resume(); - return new Response(); - }); + return new Response(); + }) + )); $this->connection->expects($this->once())->method('pause'); $this->connection->expects($this->never())->method('resume'); @@ -555,11 +606,13 @@ public function testRequestEventWithoutBodyWillNotEmitData() { $never = $this->expectCallableNever(); - $server = new Server(function (ServerRequestInterface $request) use ($never) { - $request->getBody()->on('data', $never); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($never) { + $request->getBody()->on('data', $never); - return new Response(); - }); + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -572,11 +625,13 @@ public function testRequestEventWithSecondDataEventWillEmitBodyData() { $once = $this->expectCallableOnceWith('incomplete'); - $server = new Server(function (ServerRequestInterface $request) use ($once) { - $request->getBody()->on('data', $once); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($once) { + $request->getBody()->on('data', $once); - return new Response(); - }); + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -594,11 +649,13 @@ public function testRequestEventWithPartialBodyWillEmitData() { $once = $this->expectCallableOnceWith('incomplete'); - $server = new Server(function (ServerRequestInterface $request) use ($once) { - $request->getBody()->on('data', $once); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($once) { + $request->getBody()->on('data', $once); - return new Response(); - }); + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -617,9 +674,11 @@ public function testRequestEventWithPartialBodyWillEmitData() public function testResponseContainsPoweredByHeader() { - $server = new Server(function (ServerRequestInterface $request) { - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return new Response(); + }) + )); $buffer = ''; @@ -647,9 +706,11 @@ public function testPendingPromiseWillNotSendAnything() { $never = $this->expectCallableNever(); - $server = new Server(function (ServerRequestInterface $request) use ($never) { - return new Promise(function () { }, $never); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($never) { + return new Promise(function () { }, $never); + }) + )); $buffer = ''; @@ -677,9 +738,11 @@ public function testPendingPromiseWillBeCancelledIfConnectionCloses() { $once = $this->expectCallableOnce(); - $server = new Server(function (ServerRequestInterface $request) use ($once) { - return new Promise(function () { }, $once); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($once) { + return new Promise(function () { }, $once); + }) + )); $buffer = ''; @@ -709,9 +772,11 @@ public function testStreamAlreadyClosedWillSendEmptyBodyChunkedEncoded() $stream = new ThroughStream(); $stream->close(); - $server = new Server(function (ServerRequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }) + )); $buffer = ''; @@ -740,9 +805,11 @@ public function testResponseStreamEndingWillSendEmptyBodyChunkedEncoded() { $stream = new ThroughStream(); - $server = new Server(function (ServerRequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }) + )); $buffer = ''; @@ -774,9 +841,11 @@ public function testResponseStreamAlreadyClosedWillSendEmptyBodyPlainHttp10() $stream = new ThroughStream(); $stream->close(); - $server = new Server(function (ServerRequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }) + )); $buffer = ''; @@ -806,9 +875,11 @@ public function testResponseStreamWillBeClosedIfConnectionIsAlreadyClosed() $stream = new ThroughStream(); $stream->on('close', $this->expectCallableOnce()); - $server = new Server(function (ServerRequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }) + )); $buffer = ''; @@ -857,9 +928,11 @@ public function testResponseStreamWillBeClosedIfConnectionEmitsCloseEvent() $stream = new ThroughStream(); $stream->on('close', $this->expectCallableOnce()); - $server = new Server(function (ServerRequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -871,9 +944,11 @@ public function testResponseStreamWillBeClosedIfConnectionEmitsCloseEvent() public function testUpgradeInResponseCanBeUsedToAdvertisePossibleUpgrade() { - $server = new Server(function (ServerRequestInterface $request) { - return new Response(200, array('date' => '', 'x-powered-by' => '', 'Upgrade' => 'demo'), 'foo'); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return new Response(200, array('date' => '', 'x-powered-by' => '', 'Upgrade' => 'demo'), 'foo'); + }) + )); $buffer = ''; @@ -899,9 +974,11 @@ function ($data) use (&$buffer) { public function testUpgradeWishInRequestCanBeIgnoredByReturningNormalResponse() { - $server = new Server(function (ServerRequestInterface $request) { - return new Response(200, array('date' => '', 'x-powered-by' => ''), 'foo'); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return new Response(200, array('date' => '', 'x-powered-by' => ''), 'foo'); + }) + )); $buffer = ''; @@ -927,9 +1004,11 @@ function ($data) use (&$buffer) { public function testUpgradeSwitchingProtocolIncludesConnectionUpgradeHeaderWithoutContentLength() { - $server = new Server(function (ServerRequestInterface $request) { - return new Response(101, array('date' => '', 'x-powered-by' => '', 'Upgrade' => 'demo'), 'foo'); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return new Response(101, array('date' => '', 'x-powered-by' => '', 'Upgrade' => 'demo'), 'foo'); + }) + )); $server->on('error', 'printf'); @@ -959,9 +1038,11 @@ public function testUpgradeSwitchingProtocolWithStreamWillPipeDataToConnection() { $stream = new ThroughStream(); - $server = new Server(function (ServerRequestInterface $request) use ($stream) { - return new Response(101, array('date' => '', 'x-powered-by' => '', 'Upgrade' => 'demo'), $stream); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($stream) { + return new Response(101, array('date' => '', 'x-powered-by' => '', 'Upgrade' => 'demo'), $stream); + }) + )); $buffer = ''; @@ -992,9 +1073,11 @@ public function testConnectResponseStreamWillPipeDataToConnection() { $stream = new ThroughStream(); - $server = new Server(function (ServerRequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }) + )); $buffer = ''; @@ -1026,9 +1109,11 @@ public function testConnectResponseStreamWillPipeDataFromConnection() { $stream = new ThroughStream(); - $server = new Server(function (ServerRequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1041,10 +1126,12 @@ public function testConnectResponseStreamWillPipeDataFromConnection() public function testResponseContainsSameRequestProtocolVersionAndChunkedBodyForHttp11() { - $server = new Server(function (ServerRequestInterface $request) { - $response = new Response(200, array(), 'bye'); - return \React\Promise\resolve($response); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + $response = new Response(200, array(), 'bye'); + return \React\Promise\resolve($response); + }) + )); $buffer = ''; @@ -1071,10 +1158,12 @@ function ($data) use (&$buffer) { public function testResponseContainsSameRequestProtocolVersionAndRawBodyForHttp10() { - $server = new Server(function (ServerRequestInterface $request) { - $response = new Response(200, array(), 'bye'); - return \React\Promise\resolve($response); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + $response = new Response(200, array(), 'bye'); + return \React\Promise\resolve($response); + }) + )); $buffer = ''; @@ -1102,9 +1191,11 @@ function ($data) use (&$buffer) { public function testResponseContainsNoResponseBodyForHeadRequest() { - $server = new Server(function (ServerRequestInterface $request) { - return new Response(200, array(), 'bye'); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return new Response(200, array(), 'bye'); + }) + )); $buffer = ''; $this->connection @@ -1130,9 +1221,11 @@ function ($data) use (&$buffer) { public function testResponseContainsNoResponseBodyAndNoContentLengthForNoContentStatus() { - $server = new Server(function (ServerRequestInterface $request) { - return new Response(204, array(), 'bye'); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return new Response(204, array(), 'bye'); + }) + )); $buffer = ''; $this->connection @@ -1159,9 +1252,11 @@ function ($data) use (&$buffer) { public function testResponseContainsNoResponseBodyForNotModifiedStatus() { - $server = new Server(function (ServerRequestInterface $request) { - return new Response(304, array(), 'bye'); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return new Response(304, array(), 'bye'); + }) + )); $buffer = ''; $this->connection @@ -1189,7 +1284,9 @@ function ($data) use (&$buffer) { public function testRequestInvalidHttpProtocolVersionWillEmitErrorAndSendErrorResponse() { $error = null; - $server = new Server($this->expectCallableNever()); + $server = new Server(array( + new Callback($this->expectCallableNever()) + )); $server->on('error', function ($message) use (&$error) { $error = $message; }); @@ -1223,7 +1320,9 @@ function ($data) use (&$buffer) { public function testRequestOverflowWillEmitErrorAndSendErrorResponse() { $error = null; - $server = new Server($this->expectCallableNever()); + $server = new Server(array( + new Callback($this->expectCallableNever()) + )); $server->on('error', function ($message) use (&$error) { $error = $message; }); @@ -1257,7 +1356,9 @@ function ($data) use (&$buffer) { public function testRequestInvalidWillEmitErrorAndSendErrorResponse() { $error = null; - $server = new Server($this->expectCallableNever()); + $server = new Server(array( + new Callback($this->expectCallableNever()) + )); $server->on('error', function ($message) use (&$error) { $error = $message; }); @@ -1294,14 +1395,16 @@ public function testBodyDataWillBeSendViaRequestEvent() $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }); + return \React\Promise\resolve(new Response()); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1324,15 +1427,17 @@ public function testChunkedEncodedRequestWillBeParsedForRequestEvent() $errorEvent = $this->expectCallableNever(); $requestValidation = null; - $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent, &$requestValidation) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); - $requestValidation = $request; + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent, &$requestValidation) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); + $requestValidation = $request; - return \React\Promise\resolve(new Response()); - }); + return \React\Promise\resolve(new Response()); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1357,14 +1462,16 @@ public function testChunkedEncodedRequestAdditionalDataWontBeEmitted() $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }); + return \React\Promise\resolve(new Response()); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1388,14 +1495,16 @@ public function testEmptyChunkedEncodedRequest() $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }); + return \React\Promise\resolve(new Response()); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1417,14 +1526,16 @@ public function testChunkedIsUpperCase() $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }); + return \React\Promise\resolve(new Response()); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1447,14 +1558,16 @@ public function testChunkedIsMixedUpperAndLowerCase() $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }); + return \React\Promise\resolve(new Response()); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1472,7 +1585,9 @@ public function testChunkedIsMixedUpperAndLowerCase() public function testRequestWithMalformedHostWillEmitErrorAndSendErrorResponse() { $error = null; - $server = new Server($this->expectCallableNever()); + $server = new Server(array( + new Callback($this->expectCallableNever()) + )); $server->on('error', function ($message) use (&$error) { $error = $message; }); @@ -1505,7 +1620,9 @@ function ($data) use (&$buffer) { public function testRequestWithInvalidHostUriComponentsWillEmitErrorAndSendErrorResponse() { $error = null; - $server = new Server($this->expectCallableNever()); + $server = new Server(array( + new Callback($this->expectCallableNever()) + )); $server->on('error', function ($message) use (&$error) { $error = $message; }); @@ -1542,14 +1659,16 @@ public function testWontEmitFurtherDataWhenContentLengthIsReached() $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }); + return \React\Promise\resolve(new Response()); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1573,14 +1692,16 @@ public function testWontEmitFurtherDataWhenContentLengthIsReachedSplitted() $errorEvent = $this->expectCallableNever(); - $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }); + return \React\Promise\resolve(new Response()); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1607,14 +1728,16 @@ public function testContentLengthContainsZeroWillEmitEndEvent() $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }); + return \React\Promise\resolve(new Response()); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1635,14 +1758,16 @@ public function testContentLengthContainsZeroWillEmitEndEventAdditionalDataWillB $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }); + return \React\Promise\resolve(new Response()); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1664,14 +1789,16 @@ public function testContentLengthContainsZeroWillEmitEndEventAdditionalDataWillB $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }); + return \React\Promise\resolve(new Response()); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1697,15 +1824,17 @@ public function testContentLengthWillBeIgnoredIfTransferEncodingIsSet() $errorEvent = $this->expectCallableNever(); $requestValidation = null; - $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent, &$requestValidation) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); - $requestValidation = $request; - - return \React\Promise\resolve(new Response()); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent, &$requestValidation) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); + $requestValidation = $request; + + return \React\Promise\resolve(new Response()); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1736,15 +1865,17 @@ public function testInvalidContentLengthWillBeIgnoreddIfTransferEncodingIsSet() $errorEvent = $this->expectCallableNever(); $requestValidation = null; - $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent, &$requestValidation) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); - $requestValidation = $request; - - return \React\Promise\resolve(new Response()); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent, &$requestValidation) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); + $requestValidation = $request; + + return \React\Promise\resolve(new Response()); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1771,7 +1902,9 @@ public function testInvalidContentLengthWillBeIgnoreddIfTransferEncodingIsSet() public function testNonIntegerContentLengthValueWillLeadToError() { $error = null; - $server = new Server($this->expectCallableNever()); + $server = new Server(array( + new Callback($this->expectCallableNever()) + )); $server->on('error', function ($message) use (&$error) { $error = $message; }); @@ -1808,7 +1941,9 @@ function ($data) use (&$buffer) { public function testNonIntegerContentLengthValueWillLeadToErrorWithNoBodyForHeadRequest() { $error = null; - $server = new Server($this->expectCallableNever()); + $server = new Server(array( + new Callback($this->expectCallableNever()) + )); $server->on('error', function ($message) use (&$error) { $error = $message; }); @@ -1845,7 +1980,9 @@ function ($data) use (&$buffer) { public function testMultipleIntegerInContentLengthWillLeadToError() { $error = null; - $server = new Server($this->expectCallableNever()); + $server = new Server(array( + new Callback($this->expectCallableNever()) + )); $server->on('error', function ($message) use (&$error) { $error = $message; }); @@ -1882,10 +2019,12 @@ function ($data) use (&$buffer) { public function testInvalidChunkHeaderResultsInErrorOnRequestStream() { $errorEvent = $this->expectCallableOnceWith($this->isInstanceOf('Exception')); - $server = new Server(function ($request) use ($errorEvent){ - $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }); + $server = new Server(array( + new Callback(function ($request) use ($errorEvent){ + $request->getBody()->on('error', $errorEvent); + return \React\Promise\resolve(new Response()); + }) + )); $this->connection->expects($this->never())->method('close'); $this->connection->expects($this->once())->method('pause'); @@ -1906,10 +2045,12 @@ public function testInvalidChunkHeaderResultsInErrorOnRequestStream() public function testTooLongChunkHeaderResultsInErrorOnRequestStream() { $errorEvent = $this->expectCallableOnceWith($this->isInstanceOf('Exception')); - $server = new Server(function ($request) use ($errorEvent){ - $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }); + $server = new Server(array( + new Callback(function ($request) use ($errorEvent){ + $request->getBody()->on('error', $errorEvent); + return \React\Promise\resolve(new Response()); + }) + )); $this->connection->expects($this->never())->method('close'); $this->connection->expects($this->once())->method('pause'); @@ -1932,10 +2073,12 @@ public function testTooLongChunkHeaderResultsInErrorOnRequestStream() public function testTooLongChunkBodyResultsInErrorOnRequestStream() { $errorEvent = $this->expectCallableOnceWith($this->isInstanceOf('Exception')); - $server = new Server(function ($request) use ($errorEvent){ - $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }); + $server = new Server(array( + new Callback(function ($request) use ($errorEvent){ + $request->getBody()->on('error', $errorEvent); + return \React\Promise\resolve(new Response()); + }) + )); $this->connection->expects($this->never())->method('close'); $this->connection->expects($this->once())->method('pause'); @@ -1956,10 +2099,12 @@ public function testTooLongChunkBodyResultsInErrorOnRequestStream() public function testUnexpectedEndOfConnectionWillResultsInErrorOnRequestStream() { $errorEvent = $this->expectCallableOnceWith($this->isInstanceOf('Exception')); - $server = new Server(function ($request) use ($errorEvent){ - $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }); + $server = new Server(array( + new Callback(function ($request) use ($errorEvent){ + $request->getBody()->on('error', $errorEvent); + return \React\Promise\resolve(new Response()); + }) + )); $this->connection->expects($this->never())->method('close'); $this->connection->expects($this->once())->method('pause'); @@ -1980,9 +2125,11 @@ public function testUnexpectedEndOfConnectionWillResultsInErrorOnRequestStream() public function testErrorInChunkedDecoderNeverClosesConnection() { - $server = new Server(function (ServerRequestInterface $request) { - return \React\Promise\resolve(new Response()); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return \React\Promise\resolve(new Response()); + }) + )); $this->connection->expects($this->never())->method('close'); $this->connection->expects($this->once())->method('pause'); @@ -2002,9 +2149,11 @@ public function testErrorInChunkedDecoderNeverClosesConnection() public function testErrorInLengthLimitedStreamNeverClosesConnection() { - $server = new Server(function (ServerRequestInterface $request) { - return \React\Promise\resolve(new Response()); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return \React\Promise\resolve(new Response()); + }) + )); $this->connection->expects($this->never())->method('close'); $this->connection->expects($this->once())->method('pause'); @@ -2025,10 +2174,12 @@ public function testErrorInLengthLimitedStreamNeverClosesConnection() public function testCloseRequestWillPauseConnection() { - $server = new Server(function ($request) { - $request->getBody()->close(); - return \React\Promise\resolve(new Response()); - }); + $server = new Server(array( + new Callback(function ($request) { + $request->getBody()->close(); + return \React\Promise\resolve(new Response()); + }) + )); $this->connection->expects($this->never())->method('close'); $this->connection->expects($this->once())->method('pause'); @@ -2047,14 +2198,16 @@ public function testEndEventWillBeEmittedOnSimpleRequest() $endEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(function ($request) use ($dataEvent, $closeEvent, $endEvent, $errorEvent){ - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(array( + new Callback(function ($request) use ($dataEvent, $closeEvent, $endEvent, $errorEvent){ + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }); + return \React\Promise\resolve(new Response()); + }) + )); $this->connection->expects($this->once())->method('pause'); $this->connection->expects($this->never())->method('close'); @@ -2074,14 +2227,16 @@ public function testRequestWithoutDefinedLengthWillIgnoreDataEvent() $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }); + return \React\Promise\resolve(new Response()); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -2095,10 +2250,12 @@ public function testRequestWithoutDefinedLengthWillIgnoreDataEvent() public function testResponseWillBeChunkDecodedByDefault() { $stream = new ThroughStream(); - $server = new Server(function (ServerRequestInterface $request) use ($stream) { - $response = new Response(200, array(), $stream); - return \React\Promise\resolve($response); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($stream) { + $response = new Response(200, array(), $stream); + return \React\Promise\resolve($response); + }) + )); $buffer = ''; $this->connection @@ -2126,18 +2283,20 @@ function ($data) use (&$buffer) { public function testContentLengthWillBeRemovedForResponseStream() { - $server = new Server(function (ServerRequestInterface $request) { - $response = new Response( - 200, - array( - 'Content-Length' => 5, - 'Transfer-Encoding' => 'chunked' - ), - 'hello' - ); - - return \React\Promise\resolve($response); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + $response = new Response( + 200, + array( + 'Content-Length' => 5, + 'Transfer-Encoding' => 'chunked' + ), + 'hello' + ); + + return \React\Promise\resolve($response); + }) + )); $buffer = ''; $this->connection @@ -2166,17 +2325,19 @@ function ($data) use (&$buffer) { public function testOnlyAllowChunkedEncoding() { $stream = new ThroughStream(); - $server = new Server(function (ServerRequestInterface $request) use ($stream) { - $response = new Response( - 200, - array( - 'Transfer-Encoding' => 'custom' - ), - $stream - ); - - return \React\Promise\resolve($response); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($stream) { + $response = new Response( + 200, + array( + 'Transfer-Encoding' => 'custom' + ), + $stream + ); + + return \React\Promise\resolve($response); + }) + )); $buffer = ''; $this->connection @@ -2205,9 +2366,11 @@ function ($data) use (&$buffer) { public function testDateHeaderWillBeAddedWhenNoneIsGiven() { - $server = new Server(function (ServerRequestInterface $request) { - return \React\Promise\resolve(new Response()); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return \React\Promise\resolve(new Response()); + }) + )); $buffer = ''; $this->connection @@ -2235,10 +2398,12 @@ function ($data) use (&$buffer) { public function testAddCustomDateHeader() { - $server = new Server(function (ServerRequestInterface $request) { - $response = new Response(200, array("Date" => "Tue, 15 Nov 1994 08:12:31 GMT")); - return \React\Promise\resolve($response); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + $response = new Response(200, array("Date" => "Tue, 15 Nov 1994 08:12:31 GMT")); + return \React\Promise\resolve($response); + }) + )); $buffer = ''; $this->connection @@ -2266,10 +2431,12 @@ function ($data) use (&$buffer) { public function testRemoveDateHeader() { - $server = new Server(function (ServerRequestInterface $request) { - $response = new Response(200, array('Date' => '')); - return \React\Promise\resolve($response); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + $response = new Response(200, array('Date' => '')); + return \React\Promise\resolve($response); + }) + )); $buffer = ''; $this->connection @@ -2299,7 +2466,9 @@ public function testOnlyChunkedEncodingIsAllowedForTransferEncoding() { $error = null; - $server = new Server($this->expectCallableNever()); + $server = new Server(array( + new Callback($this->expectCallableNever()) + )); $server->on('error', function ($exception) use (&$error) { $error = $exception; }); @@ -2336,7 +2505,9 @@ public function testOnlyChunkedEncodingIsAllowedForTransferEncodingWithHttp10() { $error = null; - $server = new Server($this->expectCallableNever()); + $server = new Server(array( + new Callback($this->expectCallableNever()) + )); $server->on('error', function ($exception) use (&$error) { $error = $exception; }); @@ -2369,9 +2540,11 @@ function ($data) use (&$buffer) { public function test100ContinueRequestWillBeHandled() { - $server = new Server(function (ServerRequestInterface $request) { - return \React\Promise\resolve(new Response()); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return \React\Promise\resolve(new Response()); + }) + )); $buffer = ''; $this->connection @@ -2401,9 +2574,11 @@ function ($data) use (&$buffer) { public function testContinueWontBeSendForHttp10() { - $server = new Server(function (ServerRequestInterface $request) { - return \React\Promise\resolve(new Response()); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return \React\Promise\resolve(new Response()); + }) + )); $buffer = ''; $this->connection @@ -2431,9 +2606,11 @@ function ($data) use (&$buffer) { public function testContinueWithLaterResponse() { - $server = new Server(function (ServerRequestInterface $request) { - return \React\Promise\resolve(new Response()); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return \React\Promise\resolve(new Response()); + }) + )); $buffer = ''; @@ -2463,22 +2640,16 @@ function ($data) use (&$buffer) { $this->assertContains("HTTP/1.1 200 OK\r\n", $buffer); } - /** - * @expectedException InvalidArgumentException - */ - public function testInvalidCallbackFunctionLeadsToException() - { - $server = new Server('invalid'); - } - public function testHttpBodyStreamAsBodyWillStreamData() { $input = new ThroughStream(); - $server = new Server(function (ServerRequestInterface $request) use ($input) { - $response = new Response(200, array(), $input); - return \React\Promise\resolve($response); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($input) { + $response = new Response(200, array(), $input); + return \React\Promise\resolve($response); + }) + )); $buffer = ''; $this->connection @@ -2511,10 +2682,12 @@ public function testHttpBodyStreamWithContentLengthWillStreamTillLength() { $input = new ThroughStream(); - $server = new Server(function (ServerRequestInterface $request) use ($input) { - $response = new Response(200, array('Content-Length' => 5), $input); - return \React\Promise\resolve($response); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use ($input) { + $response = new Response(200, array('Content-Length' => 5), $input); + return \React\Promise\resolve($response); + }) + )); $buffer = ''; $this->connection @@ -2546,9 +2719,11 @@ function ($data) use (&$buffer) { public function testCallbackFunctionReturnsPromise() { - $server = new Server(function (ServerRequestInterface $request) { - return \React\Promise\resolve(new Response()); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return \React\Promise\resolve(new Response()); + }) + )); $buffer = ''; $this->connection @@ -2574,9 +2749,11 @@ function ($data) use (&$buffer) { public function testReturnInvalidTypeWillResultInError() { - $server = new Server(function (ServerRequestInterface $request) { - return "invalid"; - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return "invalid"; + }) + )); $exception = null; $server->on('error', function (\Exception $ex) use (&$exception) { @@ -2610,9 +2787,11 @@ function ($data) use (&$buffer) { public function testResolveWrongTypeInPromiseWillResultInError() { - $server = new Server(function (ServerRequestInterface $request) { - return \React\Promise\resolve("invalid"); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return \React\Promise\resolve("invalid"); + }) + )); $buffer = ''; $this->connection @@ -2640,11 +2819,13 @@ function ($data) use (&$buffer) { public function testRejectedPromiseWillResultInErrorMessage() { - $server = new Server(function (ServerRequestInterface $request) { - return new Promise(function ($resolve, $reject) { - $reject(new \Exception()); - }); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return new Promise(function ($resolve, $reject) { + $reject(new \Exception()); + }); + }) + )); $server->on('error', $this->expectCallableOnce()); $buffer = ''; @@ -2673,11 +2854,13 @@ function ($data) use (&$buffer) { public function testExcpetionInCallbackWillResultInErrorMessage() { - $server = new Server(function (ServerRequestInterface $request) { - return new Promise(function ($resolve, $reject) { - throw new \Exception('Bad call'); - }); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return new Promise(function ($resolve, $reject) { + throw new \Exception('Bad call'); + }); + }) + )); $server->on('error', $this->expectCallableOnce()); $buffer = ''; @@ -2706,9 +2889,11 @@ function ($data) use (&$buffer) { public function testHeaderWillAlwaysBeContentLengthForStringBody() { - $server = new Server(function (ServerRequestInterface $request) { - return new Response(200, array('Transfer-Encoding' => 'chunked'), 'hello'); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return new Response(200, array('Transfer-Encoding' => 'chunked'), 'hello'); + }) + )); $buffer = ''; $this->connection @@ -2740,9 +2925,11 @@ function ($data) use (&$buffer) { public function testReturnRequestWillBeHandled() { - $server = new Server(function (ServerRequestInterface $request) { - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return new Response(); + }) + )); $buffer = ''; $this->connection @@ -2770,9 +2957,11 @@ function ($data) use (&$buffer) { public function testExceptionThrowInCallBackFunctionWillResultInErrorMessage() { - $server = new Server(function (ServerRequestInterface $request) { - throw new \Exception('hello'); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + throw new \Exception('hello'); + }) + )); $exception = null; $server->on('error', function (\Exception $ex) use (&$exception) { @@ -2810,9 +2999,11 @@ function ($data) use (&$buffer) { */ public function testThrowableThrowInCallBackFunctionWillResultInErrorMessage() { - $server = new Server(function (ServerRequestInterface $request) { - throw new \Error('hello'); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + throw new \Error('hello'); + }) + )); $exception = null; $server->on('error', function (\Exception $ex) use (&$exception) { @@ -2853,11 +3044,13 @@ function ($data) use (&$buffer) { public function testRejectOfNonExceptionWillResultInErrorMessage() { - $server = new Server(function (ServerRequestInterface $request) { - return new Promise(function ($resolve, $reject) { - $reject('Invalid type'); - }); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) { + return new Promise(function ($resolve, $reject) { + $reject('Invalid type'); + }); + }) + )); $exception = null; $server->on('error', function (\Exception $ex) use (&$exception) { @@ -2892,10 +3085,12 @@ function ($data) use (&$buffer) { public function testServerRequestParams() { $requestValidation = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestValidation) { - $requestValidation = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestValidation) { + $requestValidation = $request; + return new Response(); + }) + )); $this->connection ->expects($this->any()) @@ -2927,10 +3122,12 @@ public function testServerRequestParams() public function testQueryParametersWillBeAddedToRequest() { $requestValidation = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestValidation) { - $requestValidation = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestValidation) { + $requestValidation = $request; + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -2948,10 +3145,12 @@ public function testQueryParametersWillBeAddedToRequest() public function testCookieWillBeAddedToServerRequest() { $requestValidation = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestValidation) { - $requestValidation = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestValidation) { + $requestValidation = $request; + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -2970,10 +3169,12 @@ public function testCookieWillBeAddedToServerRequest() public function testMultipleCookiesWontBeAddedToServerRequest() { $requestValidation = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestValidation) { - $requestValidation = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestValidation) { + $requestValidation = $request; + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -2992,10 +3193,12 @@ public function testMultipleCookiesWontBeAddedToServerRequest() public function testCookieWithSeparatorWillBeAddedToServerRequest() { $requestValidation = null; - $server = new Server(function (ServerRequestInterface $request) use (&$requestValidation) { - $requestValidation = $request; - return new Response(); - }); + $server = new Server(array( + new Callback(function (ServerRequestInterface $request) use (&$requestValidation) { + $requestValidation = $request; + return new Response(); + }) + )); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); From 6ecd9dc8922b846920788b952bd216518c783f41 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Wed, 16 Aug 2017 08:07:24 +0200 Subject: [PATCH 02/22] Merged in master --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e4d3d117..e32a4762 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5", "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.6", "react/promise": "^2.3 || ^1.2.1", - "evenement/evenement": "^2.0 || ^1.0", + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", "react/promise-stream": "^0.1.1" }, "autoload": { From 9e318edd107e9c8fb34e4c1207fbe736290be5fb Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Wed, 16 Aug 2017 08:17:41 +0200 Subject: [PATCH 03/22] Updated Server.php documentation --- src/Server.php | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/Server.php b/src/Server.php index 9eb9d7e0..ac364757 100644 --- a/src/Server.php +++ b/src/Server.php @@ -20,18 +20,20 @@ * The `Server` class is responsible for handling incoming connections and then * processing each incoming HTTP request. * - * For each request, it executes the callback function passed to the + * For each request, it executes the middleware stack passed to the * constructor with the respective [request](#request) object and expects * a respective [response](#response) object in return. * * ```php - * $server = new Server(function (ServerRequestInterface $request) { - * return new Response( - * 200, - * array('Content-Type' => 'text/plain'), - * "Hello World!\n" - * ); - * }); + * $server = new Server([ + * new Callback(function (ServerRequestInterface $request) { + * return new Response( + * 200, + * array('Content-Type' => 'text/plain'), + * "Hello World!\n" + * ); + * }) + * ]); * ``` * * In order to process any connections, the server needs to be attached to an @@ -78,17 +80,23 @@ */ class Server extends EventEmitter { + /** + * @var MiddlewareStackInterface + */ private $middlewareStack; /** - * Creates an HTTP server that invokes the given callback for each incoming HTTP request + * Creates an HTTP server that invokes the given middleware stack for each + * incoming HTTP request. The middleware stack is either a concrete class + * implementing `React\Http\MiddlewareStackInterface` or an array of + * `React\Http\MiddlewareInterface`s. * * In order to process any connections, the server needs to be attached to an * instance of `React\Socket\ServerInterface` which emits underlying streaming * connections in order to then parse incoming data as HTTP. * See also [listen()](#listen) for more details. * - * @param MiddlewareInterface[] $middlewares + * @param MiddlewareInterface[]|MiddlewareStackInterface $middlewares * @see self::listen() */ public function __construct($middlewares) @@ -101,7 +109,7 @@ public function __construct($middlewares) return; } - if ($middlewares instanceof MiddlewareStack) { + if ($middlewares instanceof MiddlewareStackInterface) { $this->middlewareStack = $middlewares; return; } From 0447be8a5540b714c5a26a4672afcf26374c3059 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Wed, 16 Aug 2017 20:00:38 +0200 Subject: [PATCH 04/22] Updated readme code examples --- README.md | 362 +++++++++++++++++++++++++++++------------------------- 1 file changed, 197 insertions(+), 165 deletions(-) diff --git a/README.md b/README.md index 30ef680b..2c45d7bf 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,15 @@ This is an HTTP server which responds with `Hello World` to every request. ```php $loop = React\EventLoop\Factory::create(); -$server = new Server(function (ServerRequestInterface $request) { - return new Response( - 200, - array('Content-Type' => 'text/plain'), - "Hello World!\n" - ); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + return new Response( + 200, + array('Content-Type' => 'text/plain'), + "Hello World!\n" + ); + }), +]); $socket = new React\Socket\Server(8080, $loop); $server->listen($socket); @@ -50,13 +52,15 @@ constructor with the respective [request](#request) object and expects a respective [response](#response) object in return. ```php -$server = new Server(function (ServerRequestInterface $request) { - return new Response( - 200, - array('Content-Type' => 'text/plain'), - "Hello World!\n" - ); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + return new Response( + 200, + array('Content-Type' => 'text/plain'), + "Hello World!\n" + ); + }), +]); ``` In order to process any connections, the server needs to be attached to an @@ -150,16 +154,18 @@ which in turn extends the and will be passed to the callback function like this. ```php -$server = new Server(function (ServerRequestInterface $request) { - $body = "The method of the request is: " . $request->getMethod(); - $body .= "The requested path is: " . $request->getUri()->getPath(); - - return new Response( - 200, - array('Content-Type' => 'text/plain'), - $body - ); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + $body = "The method of the request is: " . $request->getMethod(); + $body .= "The requested path is: " . $request->getUri()->getPath(); + + return new Response( + 200, + array('Content-Type' => 'text/plain'), + $body + ); + }), +]); ``` The `getServerParams(): mixed[]` method can be used to @@ -184,15 +190,17 @@ The following parameters are currently available: Set to 'on' if the request used HTTPS, otherwise it won't be set ```php -$server = new Server(function (ServerRequestInterface $request) { - $body = "Your IP is: " . $request->getServerParams()['REMOTE_ADDR']; - - return new Response( - 200, - array('Content-Type' => 'text/plain'), - $body - ); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + $body = "Your IP is: " . $request->getServerParams()['REMOTE_ADDR']; + + return new Response( + 200, + array('Content-Type' => 'text/plain'), + $body + ); + }), +]); ``` See also [example #2](examples). @@ -201,22 +209,24 @@ The `getQueryParams(): array` method can be used to get the query parameters similiar to the `$_GET` variable. ```php -$server = new Server(function (ServerRequestInterface $request) { - $queryParams = $request->getQueryParams(); - - $body = 'The query parameter "foo" is not set. Click the following link '; - $body .= 'to use query parameter in your request'; - - if (isset($queryParams['foo'])) { - $body = 'The value of "foo" is: ' . htmlspecialchars($queryParams['foo']); - } - - return new Response( - 200, - array('Content-Type' => 'text/html'), - $body - ); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + $queryParams = $request->getQueryParams(); + + $body = 'The query parameter "foo" is not set. Click the following link '; + $body .= 'to use query parameter in your request'; + + if (isset($queryParams['foo'])) { + $body = 'The value of "foo" is: ' . htmlspecialchars($queryParams['foo']); + } + + return new Response( + 200, + array('Content-Type' => 'text/html'), + $body + ); + }), +]); ``` The response in the above example will return a response body with a link. @@ -264,33 +274,35 @@ Instead, you should use the `ReactPHP ReadableStreamInterface` which gives you access to the incoming request body as the individual chunks arrive: ```php -$server = new Server(function (ServerRequestInterface $request) { - return new Promise(function ($resolve, $reject) use ($request) { - $contentLength = 0; - $request->getBody()->on('data', function ($data) use (&$contentLength) { - $contentLength += strlen($data); - }); - - $request->getBody()->on('end', function () use ($resolve, &$contentLength){ - $response = new Response( - 200, - array('Content-Type' => 'text/plain'), - "The length of the submitted request body is: " . $contentLength - ); - $resolve($response); - }); - - // an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event - $request->getBody()->on('error', function (\Exception $exception) use ($resolve, &$contentLength) { - $response = new Response( - 400, - array('Content-Type' => 'text/plain'), - "An error occured while reading at length: " . $contentLength - ); - $resolve($response); - }); - }); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + return new Promise(function ($resolve, $reject) use ($request) { + $contentLength = 0; + $request->getBody()->on('data', function ($data) use (&$contentLength) { + $contentLength += strlen($data); + }); + + $request->getBody()->on('end', function () use ($resolve, &$contentLength){ + $response = new Response( + 200, + array('Content-Type' => 'text/plain'), + "The length of the submitted request body is: " . $contentLength + ); + $resolve($response); + }); + + // an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event + $request->getBody()->on('error', function (\Exception $exception) use ($resolve, &$contentLength) { + $response = new Response( + 400, + array('Content-Type' => 'text/plain'), + "An error occured while reading at length: " . $contentLength + ); + $resolve($response); + }); + }) + }), +]); ``` The above example simply counts the number of bytes received in the request body. @@ -328,25 +340,27 @@ Note that this value may be `null` if the request body size is unknown in advance because the request message uses chunked transfer encoding. ```php -$server = new Server(function (ServerRequestInterface $request) { - $size = $request->getBody()->getSize(); - if ($size === null) { - $body = 'The request does not contain an explicit length.'; - $body .= 'This server does not accept chunked transfer encoding.'; - +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + $size = $request->getBody()->getSize(); + if ($size === null) { + $body = 'The request does not contain an explicit length.'; + $body .= 'This server does not accept chunked transfer encoding.'; + + return new Response( + 411, + array('Content-Type' => 'text/plain'), + $body + ); + } + return new Response( - 411, + 200, array('Content-Type' => 'text/plain'), - $body + "Request body size: " . $size . " bytes\n" ); - } - - return new Response( - 200, - array('Content-Type' => 'text/plain'), - "Request body size: " . $size . " bytes\n" - ); -}); + }), +]); ``` Note that the server supports *any* request method (including custom and non- @@ -382,28 +396,30 @@ The `getCookieParams(): string[]` method can be used to get all cookies sent with the current request. ```php -$server = new Server(function (ServerRequestInterface $request) { - $key = 'react\php'; - - if (isset($request->getCookieParams()[$key])) { - $body = "Your cookie value is: " . $request->getCookieParams()[$key]; - +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + $key = 'react\php'; + + if (isset($request->getCookieParams()[$key])) { + $body = "Your cookie value is: " . $request->getCookieParams()[$key]; + + return new Response( + 200, + array('Content-Type' => 'text/plain'), + $body + ); + } + return new Response( 200, - array('Content-Type' => 'text/plain'), - $body + array( + 'Content-Type' => 'text/plain', + 'Set-Cookie' => urlencode($key) . '=' . urlencode('test;more') + ), + "Your cookie has been set." ); - } - - return new Response( - 200, - array( - 'Content-Type' => 'text/plain', - 'Set-Cookie' => urlencode($key) . '=' . urlencode('test;more') - ), - "Your cookie has been set." - ); -}); + }), +]); ``` The above example will try to set a cookie on first access and @@ -433,13 +449,15 @@ but feel free to use any implemantation of the `PSR-7 ResponseInterface` you prefer. ```php -$server = new Server(function (ServerRequestInterface $request) { - return new Response( - 200, - array('Content-Type' => 'text/plain'), - "Hello World!\n" - ); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + return new Response( + 200, + array('Content-Type' => 'text/plain'), + "Hello World!\n" + ); + }), +]); ``` The example above returns the response directly, because it needs @@ -452,18 +470,20 @@ To prevent this you SHOULD use a This example shows how such a long-term action could look like: ```php -$server = new Server(function (ServerRequestInterface $request) use ($loop) { - return new Promise(function ($resolve, $reject) use ($request, $loop) { - $loop->addTimer(1.5, function() use ($loop, $resolve) { - $response = new Response( - 200, - array('Content-Type' => 'text/plain'), - "Hello world" - ); - $resolve($response); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) use ($loop) { + return new Promise(function ($resolve, $reject) use ($request, $loop) { + $loop->addTimer(1.5, function() use ($loop, $resolve) { + $response = new Response( + 200, + array('Content-Type' => 'text/plain'), + "Hello world" + ); + $resolve($response); + }); }); - }); -}); + }), +]); ``` The above example will create a response after 1.5 second. @@ -485,20 +505,22 @@ Note that other implementations of the `PSR-7 ResponseInterface` likely only support strings. ```php -$server = new Server(function (ServerRequestInterface $request) use ($loop) { - $stream = new ThroughStream(); - - $timer = $loop->addPeriodicTimer(0.5, function () use ($stream) { - $stream->emit('data', array(microtime(true) . PHP_EOL)); - }); - - $loop->addTimer(5, function() use ($loop, $timer, $stream) { - $loop->cancelTimer($timer); - $stream->emit('end'); - }); - - return new Response(200, array('Content-Type' => 'text/plain'), $stream); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) use ($loop) { + $stream = new ThroughStream(); + + $timer = $loop->addPeriodicTimer(0.5, function () use ($stream) { + $stream->emit('data', array(microtime(true) . PHP_EOL)); + }); + + $loop->addTimer(5, function() use ($loop, $timer, $stream) { + $loop->cancelTimer($timer); + $stream->emit('end'); + }); + + return new Response(200, array('Content-Type' => 'text/plain'), $stream); + }), +]); ``` The above example will emit every 0.5 seconds the current Unix timestamp @@ -528,16 +550,18 @@ If you know the length of your stream body, you MAY specify it like this instead ```php $stream = new ThroughStream() -$server = new Server(function (ServerRequestInterface $request) use ($stream) { - return new Response( - 200, - array( - 'Content-Length' => '5', - 'Content-Type' => 'text/plain', - ), - $stream - ); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) use ($stream) { + return new Response( + 200, + array( + 'Content-Length' => '5', + 'Content-Type' => 'text/plain', + ), + $stream + ); + }), +]); ``` An invalid return value or an unhandled `Exception` or `Throwable` in the code @@ -613,36 +637,44 @@ A `Date` header will be automatically added with the system date and time if non You can add a custom `Date` header yourself like this: ```php -$server = new Server(function (ServerRequestInterface $request) { - return new Response(200, array('Date' => date('D, d M Y H:i:s T'))); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + return new Response(200, array('Date' => date('D, d M Y H:i:s T'))); + }), +]); ``` If you don't have a appropriate clock to rely on, you should unset this header with an empty string: ```php -$server = new Server(function (ServerRequestInterface $request) { - return new Response(200, array('Date' => '')); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + return new Response(200, array('Date' => '')); + }), +]); ``` Note that it will automatically assume a `X-Powered-By: react/alpha` header unless your specify a custom `X-Powered-By` header yourself: ```php -$server = new Server(function (ServerRequestInterface $request) { - return new Response(200, array('X-Powered-By' => 'PHP 3')); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + return new Response(200, array('X-Powered-By' => 'PHP 3')); + }), +]); ``` If you do not want to send this header at all, you can use an empty string as value like this: ```php -$server = new Server(function (ServerRequestInterface $request) { - return new Response(200, array('X-Powered-By' => '')); -}); +$server = new Server([ + new Callback(function (ServerRequestInterface $request) { + return new Response(200, array('X-Powered-By' => '')); + }), +]); ``` Note that persistent connections (`Connection: keep-alive`) are currently From 1eac846391959d8cc90acf6d9423f6baea2e9dfe Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 17 Aug 2017 08:08:49 +0200 Subject: [PATCH 05/22] Use MiddlewareStack directly in example 13 --- examples/13-request-sec.php | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/examples/13-request-sec.php b/examples/13-request-sec.php index 4c3cd2de..9b216f37 100644 --- a/examples/13-request-sec.php +++ b/examples/13-request-sec.php @@ -4,6 +4,7 @@ use React\EventLoop\Factory; use React\Http\Middleware\Callback; use React\Http\MiddlewareInterface; +use React\Http\MiddlewareStack; use React\Http\MiddlewareStackInterface; use React\Http\Response; use React\Http\Server; @@ -43,22 +44,25 @@ public function process(ServerRequestInterface $request, MiddlewareStackInterfac 'responses' => 0, ); }); -$server = new Server(array( - new Incre($counts), - new Callback(function (ServerRequestInterface $request) use ($loop) { - $deferred = new Deferred(); - $loop->addTimer(mt_rand(1, 10) / 10, function () use ($deferred) { - $deferred->resolve(new Response( - 200, - array( - 'Content-Type' => 'text/plain' - ), - "Hello world\n" - )); - }); - return $deferred->promise(); - }) -)); +$server = new Server(new MiddlewareStack( + new Response(500), + array( + new Incre($counts), + new Callback(function (ServerRequestInterface $request) use ($loop) { + $deferred = new Deferred(); + $loop->addTimer(mt_rand(1, 10) / 10, function () use ($deferred) { + $deferred->resolve(new Response( + 200, + array( + 'Content-Type' => 'text/plain' + ), + "Hello world\n" + )); + }); + return $deferred->promise(); + }) + )) +); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); From f7fa17f3dc2d0ee16d30f17af7dc5e8d1cb2adba Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 17 Aug 2017 22:14:28 +0200 Subject: [PATCH 06/22] Document middleware in the readme --- README.md | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2c45d7bf..eff62a02 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Event-driven, streaming plaintext HTTP and secure HTTPS server for [ReactPHP](ht * [Quickstart example](#quickstart-example) * [Usage](#usage) * [Server](#server) + * [Middleware](#middleware) * [Request](#request) * [Response](#response) * [Install](#install) @@ -47,7 +48,7 @@ See also the [examples](examples). The `Server` class is responsible for handling incoming connections and then processing each incoming HTTP request. -For each request, it executes the callback function passed to the +For each request, it executes the middleware stack passed to the constructor with the respective [request](#request) object and expects a respective [response](#response) object in return. @@ -72,7 +73,7 @@ You can attach this to a in order to start a plaintext HTTP server like this: ```php -$server = new Server($handler); +$server = new Server($middlewareStack); $socket = new React\Socket\Server(8080, $loop); $server->listen($socket); @@ -85,7 +86,7 @@ Similarly, you can also attach this to a in order to start a secure HTTPS server like this: ```php -$server = new Server($handler); +$server = new Server($middlewareStack); $socket = new React\Socket\Server(8080, $loop); $socket = new React\Socket\SecureServer($socket, $loop, array( @@ -140,6 +141,88 @@ $server->on('error', function (Exception $e) { Note that the request object can also emit an error. Check out [request](#request) for more details. +### Middleware + +As of `0.8` request handling is done via middlewares either by passing +an array with middlewares implementing +[`React\Http\MiddlewareInterface`](src/MiddlewareInterface.php) +or by passing a concrete implementation of +[`React\Http\MiddlewareStackInterface`](src/MiddlewareStackInterface.php) +directly. + +Middlewares implementing [`React\Http\MiddlewareInterface`](src/MiddlewareInterface.php) +are expected to return a promise resolving a +`Psr\Http\Message\ResponseInterface`. +However what ever comes out of the middleware is run through +[`resolve()`](https://github.com/reactphp/promise#resolve) +and turned into a promise regardless. When a middleware can't resolve the +request to a promise it is expected to delegate that task to the passed +[`React\Http\MiddlewareStackInterface`](src/MiddlewareStackInterface.php). +This is process is inspired by the work in progress PSR-15 with the +difference that promises are returned instead of responses. + +See also [example #12](examples) and [example #13](examples). + +#### Callback + +A few build in middlewares are shipped with this package, one of them is +the `Callback` middleware. This middlewares takes a `callable` and executes +it on each request. Identically to how this package worked before `0.8`. + +Usage: + +```php +$middlewares = [ + new Callback(function (ServerRequestInterface $request) { + return new Response(200); + }), +]; +``` + +#### Buffer + +Another build in middleware is `Buffer` which will buffer the incoming +request body until the reported size has been reached. Then it will +call the middleware stack with the new request instance containing the +full request body. + +Usage: + +```php +$middlewares = [ + new Buffer(), +]; +``` + +#### LimitHandlers + +The `LimitHandlers` middleware is all about concurrency control and will only allow the +given number of requests to be handled concurrently. + +Usage: + +```php +$middlewares = [ + new LimitHandlers(10), +]; +``` + +#### Stacking middlewares + +All these middlewares can of course be combined into a stack. The following example limits +to 5 concurrent handlers, buffers the request body before handling and returns a empty 200 +response. + +```php +$middlewares = [ + new LimitHandlers(5), + new Buffer(), + new Callback(function (ServerRequestInterface $request) { + return new Response(200); + }), +]; +``` + ### Request An seen above, the `Server` class is responsible for handling incoming From f27febb1bc22d3be96050e3b7124ed44ed439f16 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 19 Aug 2017 08:38:29 +0200 Subject: [PATCH 07/22] Removed PSR15RecoilMiddlewareStack as it is implemented in a for package --- src/PSR15RecoilMiddlewareStack.php | 65 ------------------------------ 1 file changed, 65 deletions(-) delete mode 100644 src/PSR15RecoilMiddlewareStack.php diff --git a/src/PSR15RecoilMiddlewareStack.php b/src/PSR15RecoilMiddlewareStack.php deleted file mode 100644 index e18c8d41..00000000 --- a/src/PSR15RecoilMiddlewareStack.php +++ /dev/null @@ -1,65 +0,0 @@ -kernel = $kernel; - $this->defaultResponse = $response; - $this->middlewares = $middlewares; - } - - public function process(ServerRequestInterface $request, DelegateInterface $stack) - { - if (count($this->middlewares) === 0) { - return Promise\resolve($this->defaultResponse); - } - - $middlewares = $this->middlewares; - $middleware = array_shift($middlewares); - - return new Promise\Promise(function ($resolve, $reject) { - $this->kernel->execute(function () use ($resolve, $reject) { - $middleware->process( - $request, - new self( - $this->defaultResponse, - $middlewares - ) - ); - }); - }); - } -} From ad7f8bbc58dfca9a7ed46075a0b577c1041c6581 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 19 Aug 2017 09:45:03 +0200 Subject: [PATCH 08/22] Removed unused class imports from Server.php --- src/Server.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Server.php b/src/Server.php index ac364757..c56985cd 100644 --- a/src/Server.php +++ b/src/Server.php @@ -3,9 +3,6 @@ namespace React\Http; use Evenement\EventEmitter; -use Interop\Http\ServerMiddleware\DelegateInterface; -use Interop\Http\ServerMiddleware\MiddlewareInterface; -use React\Http\Middleware\Callback; use React\Socket\ServerInterface; use React\Socket\ConnectionInterface; use Psr\Http\Message\RequestInterface; From 4699ea06c1376ed979cafc7e612517c53c4c2a62 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 19 Aug 2017 16:16:45 +0200 Subject: [PATCH 09/22] Removed unused class imports from MiddlewareStack.php --- src/MiddlewareStack.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MiddlewareStack.php b/src/MiddlewareStack.php index 4fda2288..3b91be2a 100644 --- a/src/MiddlewareStack.php +++ b/src/MiddlewareStack.php @@ -2,7 +2,6 @@ namespace React\Http; -use Interop\Http\ServerMiddleware\MiddlewareInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use React\Promise; From 9c3a09a52b94cb3409d7770e8d6d81dbe7a303e5 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 19 Aug 2017 16:17:47 +0200 Subject: [PATCH 10/22] Allow both ResponseInterface and PromiseInterface as return types MiddlewareInterface::process --- src/MiddlewareInterface.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/MiddlewareInterface.php b/src/MiddlewareInterface.php index 787b0e1c..ef20d948 100644 --- a/src/MiddlewareInterface.php +++ b/src/MiddlewareInterface.php @@ -2,6 +2,7 @@ namespace React\Http; +use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use React\Promise\PromiseInterface; @@ -14,7 +15,7 @@ interface MiddlewareInterface * @param ServerRequestInterface $request * @param MiddlewareStackInterface $stack * - * @return PromiseInterface + * @return ResponseInterface|PromiseInterface */ public function process(ServerRequestInterface $request, MiddlewareStackInterface $stack); } From ba9e744fbe612404fd7acaed37ea5a8272630a8b Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 19 Aug 2017 16:22:15 +0200 Subject: [PATCH 11/22] Added supported types for Server::__construct InvalidArgumentException --- src/Server.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Server.php b/src/Server.php index c56985cd..e96691eb 100644 --- a/src/Server.php +++ b/src/Server.php @@ -111,7 +111,7 @@ public function __construct($middlewares) return; } - throw new \InvalidArgumentException(); + throw new \InvalidArgumentException('Only MiddlewareInterface[] or MiddlewareStackInterface implementations are supported'); } /** From ece043b0f32be8572779efceb14097b69f8b0851 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 19 Aug 2017 17:20:44 +0200 Subject: [PATCH 12/22] When passing a callable to Server::__construct it is automatically wrapped into a middleware stack; restoring old behavior while still supporting middleware --- README.md | 378 +++++----- examples/01-hello-world.php | 21 +- examples/02-count-visitors.php | 17 +- examples/03-client-ip.php | 21 +- examples/04-query-parameter.php | 29 +- examples/05-cookie-handling.php | 37 +- examples/06-sleep.php | 25 +- examples/07-error-handling.php | 37 +- examples/08-stream-response.php | 47 +- examples/09-stream-request.php | 55 +- examples/11-hello-world-https.php | 17 +- examples/12-middleware.php | 4 +- examples/21-http-proxy.php | 49 +- examples/22-connect-proxy.php | 55 +- examples/31-upgrade-echo.php | 47 +- examples/32-upgrade-chat.php | 87 ++- examples/99-benchmark-download.php | 51 +- src/Server.php | 11 +- tests/ServerTest.php | 1055 +++++++++++----------------- 19 files changed, 885 insertions(+), 1158 deletions(-) diff --git a/README.md b/README.md index eff62a02..7028bbd5 100644 --- a/README.md +++ b/README.md @@ -23,15 +23,13 @@ This is an HTTP server which responds with `Hello World` to every request. ```php $loop = React\EventLoop\Factory::create(); -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - return new Response( - 200, - array('Content-Type' => 'text/plain'), - "Hello World!\n" - ); - }), -]); +$server = new Server(function (ServerRequestInterface $request) { + return new Response( + 200, + array('Content-Type' => 'text/plain'), + "Hello World!\n" + ); +}); $socket = new React\Socket\Server(8080, $loop); $server->listen($socket); @@ -48,20 +46,18 @@ See also the [examples](examples). The `Server` class is responsible for handling incoming connections and then processing each incoming HTTP request. -For each request, it executes the middleware stack passed to the +For each request, it executes the callback function passed to the constructor with the respective [request](#request) object and expects a respective [response](#response) object in return. ```php -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - return new Response( - 200, - array('Content-Type' => 'text/plain'), - "Hello World!\n" - ); - }), -]); +$server = new Server(function (ServerRequestInterface $request) { + return new Response( + 200, + array('Content-Type' => 'text/plain'), + "Hello World!\n" + ); +}); ``` In order to process any connections, the server needs to be attached to an @@ -73,7 +69,7 @@ You can attach this to a in order to start a plaintext HTTP server like this: ```php -$server = new Server($middlewareStack); +$server = new Server($handler); $socket = new React\Socket\Server(8080, $loop); $server->listen($socket); @@ -86,7 +82,7 @@ Similarly, you can also attach this to a in order to start a secure HTTPS server like this: ```php -$server = new Server($middlewareStack); +$server = new Server($handler); $socket = new React\Socket\Server(8080, $loop); $socket = new React\Socket\SecureServer($socket, $loop, array( @@ -143,10 +139,10 @@ Check out [request](#request) for more details. ### Middleware -As of `0.8` request handling is done via middlewares either by passing -an array with middlewares implementing -[`React\Http\MiddlewareInterface`](src/MiddlewareInterface.php) -or by passing a concrete implementation of +As of `0.8` request handling is done via middlewares either by passing +a `callable` as before, an array with middlewares implementing +[`React\Http\MiddlewareInterface`](src/MiddlewareInterface.php), +passing a concrete implementation of [`React\Http\MiddlewareStackInterface`](src/MiddlewareStackInterface.php) directly. @@ -237,18 +233,16 @@ which in turn extends the and will be passed to the callback function like this. ```php -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - $body = "The method of the request is: " . $request->getMethod(); - $body .= "The requested path is: " . $request->getUri()->getPath(); - - return new Response( - 200, - array('Content-Type' => 'text/plain'), - $body - ); - }), -]); +$server = new Server(function (ServerRequestInterface $request) { + $body = "The method of the request is: " . $request->getMethod(); + $body .= "The requested path is: " . $request->getUri()->getPath(); + + return new Response( + 200, + array('Content-Type' => 'text/plain'), + $body + ); +}); ``` The `getServerParams(): mixed[]` method can be used to @@ -273,17 +267,15 @@ The following parameters are currently available: Set to 'on' if the request used HTTPS, otherwise it won't be set ```php -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - $body = "Your IP is: " . $request->getServerParams()['REMOTE_ADDR']; - - return new Response( - 200, - array('Content-Type' => 'text/plain'), - $body - ); - }), -]); +$server = new Server(function (ServerRequestInterface $request) { + $body = "Your IP is: " . $request->getServerParams()['REMOTE_ADDR']; + + return new Response( + 200, + array('Content-Type' => 'text/plain'), + $body + ); +}); ``` See also [example #2](examples). @@ -292,24 +284,22 @@ The `getQueryParams(): array` method can be used to get the query parameters similiar to the `$_GET` variable. ```php -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - $queryParams = $request->getQueryParams(); - - $body = 'The query parameter "foo" is not set. Click the following link '; - $body .= 'to use query parameter in your request'; - - if (isset($queryParams['foo'])) { - $body = 'The value of "foo" is: ' . htmlspecialchars($queryParams['foo']); - } - - return new Response( - 200, - array('Content-Type' => 'text/html'), - $body - ); - }), -]); +$server = new Server(function (ServerRequestInterface $request) { + $queryParams = $request->getQueryParams(); + + $body = 'The query parameter "foo" is not set. Click the following link '; + $body .= 'to use query parameter in your request'; + + if (isset($queryParams['foo'])) { + $body = 'The value of "foo" is: ' . htmlspecialchars($queryParams['foo']); + } + + return new Response( + 200, + array('Content-Type' => 'text/html'), + $body + ); +}); ``` The response in the above example will return a response body with a link. @@ -357,35 +347,33 @@ Instead, you should use the `ReactPHP ReadableStreamInterface` which gives you access to the incoming request body as the individual chunks arrive: ```php -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - return new Promise(function ($resolve, $reject) use ($request) { - $contentLength = 0; - $request->getBody()->on('data', function ($data) use (&$contentLength) { - $contentLength += strlen($data); - }); - - $request->getBody()->on('end', function () use ($resolve, &$contentLength){ - $response = new Response( - 200, - array('Content-Type' => 'text/plain'), - "The length of the submitted request body is: " . $contentLength - ); - $resolve($response); - }); - - // an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event - $request->getBody()->on('error', function (\Exception $exception) use ($resolve, &$contentLength) { - $response = new Response( - 400, - array('Content-Type' => 'text/plain'), - "An error occured while reading at length: " . $contentLength - ); - $resolve($response); - }); - }) - }), -]); +$server = new Server(function (ServerRequestInterface $request) { + return new Promise(function ($resolve, $reject) use ($request) { + $contentLength = 0; + $request->getBody()->on('data', function ($data) use (&$contentLength) { + $contentLength += strlen($data); + }); + + $request->getBody()->on('end', function () use ($resolve, &$contentLength){ + $response = new Response( + 200, + array('Content-Type' => 'text/plain'), + "The length of the submitted request body is: " . $contentLength + ); + $resolve($response); + }); + + // an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event + $request->getBody()->on('error', function (\Exception $exception) use ($resolve, &$contentLength) { + $response = new Response( + 400, + array('Content-Type' => 'text/plain'), + "An error occured while reading at length: " . $contentLength + ); + $resolve($response); + }); + }); +}); ``` The above example simply counts the number of bytes received in the request body. @@ -423,27 +411,25 @@ Note that this value may be `null` if the request body size is unknown in advance because the request message uses chunked transfer encoding. ```php -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - $size = $request->getBody()->getSize(); - if ($size === null) { - $body = 'The request does not contain an explicit length.'; - $body .= 'This server does not accept chunked transfer encoding.'; - - return new Response( - 411, - array('Content-Type' => 'text/plain'), - $body - ); - } - +$server = new Server(function (ServerRequestInterface $request) { + $size = $request->getBody()->getSize(); + if ($size === null) { + $body = 'The request does not contain an explicit length.'; + $body .= 'This server does not accept chunked transfer encoding.'; + return new Response( - 200, + 411, array('Content-Type' => 'text/plain'), - "Request body size: " . $size . " bytes\n" + $body ); - }), -]); + } + + return new Response( + 200, + array('Content-Type' => 'text/plain'), + "Request body size: " . $size . " bytes\n" + ); +}); ``` Note that the server supports *any* request method (including custom and non- @@ -479,30 +465,28 @@ The `getCookieParams(): string[]` method can be used to get all cookies sent with the current request. ```php -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - $key = 'react\php'; - - if (isset($request->getCookieParams()[$key])) { - $body = "Your cookie value is: " . $request->getCookieParams()[$key]; - - return new Response( - 200, - array('Content-Type' => 'text/plain'), - $body - ); - } - +$server = new Server(function (ServerRequestInterface $request) { + $key = 'react\php'; + + if (isset($request->getCookieParams()[$key])) { + $body = "Your cookie value is: " . $request->getCookieParams()[$key]; + return new Response( 200, - array( - 'Content-Type' => 'text/plain', - 'Set-Cookie' => urlencode($key) . '=' . urlencode('test;more') - ), - "Your cookie has been set." + array('Content-Type' => 'text/plain'), + $body ); - }), -]); + } + + return new Response( + 200, + array( + 'Content-Type' => 'text/plain', + 'Set-Cookie' => urlencode($key) . '=' . urlencode('test;more') + ), + "Your cookie has been set." + ); +}); ``` The above example will try to set a cookie on first access and @@ -532,15 +516,13 @@ but feel free to use any implemantation of the `PSR-7 ResponseInterface` you prefer. ```php -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - return new Response( - 200, - array('Content-Type' => 'text/plain'), - "Hello World!\n" - ); - }), -]); +$server = new Server(function (ServerRequestInterface $request) { + return new Response( + 200, + array('Content-Type' => 'text/plain'), + "Hello World!\n" + ); +}); ``` The example above returns the response directly, because it needs @@ -553,20 +535,18 @@ To prevent this you SHOULD use a This example shows how such a long-term action could look like: ```php -$server = new Server([ - new Callback(function (ServerRequestInterface $request) use ($loop) { - return new Promise(function ($resolve, $reject) use ($request, $loop) { - $loop->addTimer(1.5, function() use ($loop, $resolve) { - $response = new Response( - 200, - array('Content-Type' => 'text/plain'), - "Hello world" - ); - $resolve($response); - }); +$server = new Server(function (ServerRequestInterface $request) use ($loop) { + return new Promise(function ($resolve, $reject) use ($request, $loop) { + $loop->addTimer(1.5, function() use ($loop, $resolve) { + $response = new Response( + 200, + array('Content-Type' => 'text/plain'), + "Hello world" + ); + $resolve($response); }); - }), -]); + }); +}); ``` The above example will create a response after 1.5 second. @@ -588,22 +568,20 @@ Note that other implementations of the `PSR-7 ResponseInterface` likely only support strings. ```php -$server = new Server([ - new Callback(function (ServerRequestInterface $request) use ($loop) { - $stream = new ThroughStream(); - - $timer = $loop->addPeriodicTimer(0.5, function () use ($stream) { - $stream->emit('data', array(microtime(true) . PHP_EOL)); - }); - - $loop->addTimer(5, function() use ($loop, $timer, $stream) { - $loop->cancelTimer($timer); - $stream->emit('end'); - }); - - return new Response(200, array('Content-Type' => 'text/plain'), $stream); - }), -]); +$server = new Server(function (ServerRequestInterface $request) use ($loop) { + $stream = new ThroughStream(); + + $timer = $loop->addPeriodicTimer(0.5, function () use ($stream) { + $stream->emit('data', array(microtime(true) . PHP_EOL)); + }); + + $loop->addTimer(5, function() use ($loop, $timer, $stream) { + $loop->cancelTimer($timer); + $stream->emit('end'); + }); + + return new Response(200, array('Content-Type' => 'text/plain'), $stream); +}); ``` The above example will emit every 0.5 seconds the current Unix timestamp @@ -633,18 +611,16 @@ If you know the length of your stream body, you MAY specify it like this instead ```php $stream = new ThroughStream() -$server = new Server([ - new Callback(function (ServerRequestInterface $request) use ($stream) { - return new Response( - 200, - array( - 'Content-Length' => '5', - 'Content-Type' => 'text/plain', - ), - $stream - ); - }), -]); +$server = new Server(function (ServerRequestInterface $request) use ($stream) { + return new Response( + 200, + array( + 'Content-Length' => '5', + 'Content-Type' => 'text/plain', + ), + $stream + ); +}); ``` An invalid return value or an unhandled `Exception` or `Throwable` in the code @@ -720,44 +696,36 @@ A `Date` header will be automatically added with the system date and time if non You can add a custom `Date` header yourself like this: ```php -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - return new Response(200, array('Date' => date('D, d M Y H:i:s T'))); - }), -]); +$server = new Server(function (ServerRequestInterface $request) { + return new Response(200, array('Date' => date('D, d M Y H:i:s T'))); +}); ``` If you don't have a appropriate clock to rely on, you should unset this header with an empty string: ```php -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - return new Response(200, array('Date' => '')); - }), -]); +$server = new Server(function (ServerRequestInterface $request) { + return new Response(200, array('Date' => '')); +}); ``` Note that it will automatically assume a `X-Powered-By: react/alpha` header unless your specify a custom `X-Powered-By` header yourself: ```php -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - return new Response(200, array('X-Powered-By' => 'PHP 3')); - }), -]); +$server = new Server(function (ServerRequestInterface $request) { + return new Response(200, array('X-Powered-By' => 'PHP 3')); +}); ``` If you do not want to send this header at all, you can use an empty string as value like this: ```php -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - return new Response(200, array('X-Powered-By' => '')); - }), -]); +$server = new Server(function (ServerRequestInterface $request) { + return new Response(200, array('X-Powered-By' => '')); +}); ``` Note that persistent connections (`Connection: keep-alive`) are currently @@ -774,7 +742,7 @@ The recommended way to install this library is [through Composer](http://getcomp This will install the latest supported version: ```bash -$ composer require react/http:^0.7.3 +$ composer require react/http:^0.7.4 ``` More details about version upgrades can be found in the [CHANGELOG](CHANGELOG.md). diff --git a/examples/01-hello-world.php b/examples/01-hello-world.php index ce2f592b..f703a5d7 100644 --- a/examples/01-hello-world.php +++ b/examples/01-hello-world.php @@ -2,7 +2,6 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; -use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; @@ -10,17 +9,15 @@ $loop = Factory::create(); -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - return new Response( - 200, - array( - 'Content-Type' => 'text/plain' - ), - "Hello world\n" - ); - }) -]); +$server = new Server(function (ServerRequestInterface $request) { + return new Response( + 200, + array( + 'Content-Type' => 'text/plain' + ), + "Hello world\n" + ); +}); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/02-count-visitors.php b/examples/02-count-visitors.php index da2ab6c9..5a225110 100644 --- a/examples/02-count-visitors.php +++ b/examples/02-count-visitors.php @@ -2,7 +2,6 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; -use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; @@ -11,15 +10,13 @@ $loop = Factory::create(); $counter = 0; -$server = new Server([ - new Callback(function (ServerRequestInterface $request) use (&$counter) { - return new Response( - 200, - array('Content-Type' => 'text/plain'), - "Welcome number " . ++$counter . "!\n" - ); - }) -]); +$server = new Server(function (ServerRequestInterface $request) use (&$counter) { + return new Response( + 200, + array('Content-Type' => 'text/plain'), + "Welcome number " . ++$counter . "!\n" + ); +}); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/03-client-ip.php b/examples/03-client-ip.php index c33c5d72..3fbcabfd 100644 --- a/examples/03-client-ip.php +++ b/examples/03-client-ip.php @@ -2,7 +2,6 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; -use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; @@ -10,17 +9,15 @@ $loop = Factory::create(); -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - $body = "Your IP is: " . $request->getServerParams()['REMOTE_ADDR']; - - return new Response( - 200, - array('Content-Type' => 'text/plain'), - $body - ); - }) -]); +$server = new Server(function (ServerRequestInterface $request) { + $body = "Your IP is: " . $request->getServerParams()['REMOTE_ADDR']; + + return new Response( + 200, + array('Content-Type' => 'text/plain'), + $body + ); +}); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/04-query-parameter.php b/examples/04-query-parameter.php index a41490d8..3a60aae8 100644 --- a/examples/04-query-parameter.php +++ b/examples/04-query-parameter.php @@ -2,7 +2,6 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; -use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; @@ -10,24 +9,22 @@ $loop = Factory::create(); -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - $queryParams = $request->getQueryParams(); +$server = new Server(function (ServerRequestInterface $request) { + $queryParams = $request->getQueryParams(); - $body = 'The query parameter "foo" is not set. Click the following link '; - $body .= 'to use query parameter in your request'; + $body = 'The query parameter "foo" is not set. Click the following link '; + $body .= 'to use query parameter in your request'; - if (isset($queryParams['foo'])) { - $body = 'The value of "foo" is: ' . htmlspecialchars($queryParams['foo']); - } + if (isset($queryParams['foo'])) { + $body = 'The value of "foo" is: ' . htmlspecialchars($queryParams['foo']); + } - return new Response( - 200, - array('Content-Type' => 'text/html'), - $body - ); - }) -]); + return new Response( + 200, + array('Content-Type' => 'text/html'), + $body + ); +}); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/05-cookie-handling.php b/examples/05-cookie-handling.php index 9e907d5b..5441adbe 100644 --- a/examples/05-cookie-handling.php +++ b/examples/05-cookie-handling.php @@ -2,7 +2,6 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; -use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; @@ -10,30 +9,28 @@ $loop = Factory::create(); -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - $key = 'react\php'; +$server = new Server(function (ServerRequestInterface $request) { + $key = 'react\php'; - if (isset($request->getCookieParams()[$key])) { - $body = "Your cookie value is: " . $request->getCookieParams()[$key]; - - return new Response( - 200, - array('Content-Type' => 'text/plain'), - $body - ); - } + if (isset($request->getCookieParams()[$key])) { + $body = "Your cookie value is: " . $request->getCookieParams()[$key]; return new Response( 200, - array( - 'Content-Type' => 'text/plain', - 'Set-Cookie' => urlencode($key) . '=' . urlencode('test;more') - ), - "Your cookie has been set." + array('Content-Type' => 'text/plain'), + $body ); - }) -]); + } + + return new Response( + 200, + array( + 'Content-Type' => 'text/plain', + 'Set-Cookie' => urlencode($key) . '=' . urlencode('test;more') + ), + "Your cookie has been set." + ); +}); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/06-sleep.php b/examples/06-sleep.php index 51293c6e..926aac10 100644 --- a/examples/06-sleep.php +++ b/examples/06-sleep.php @@ -2,7 +2,6 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; -use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; use React\Promise\Promise; @@ -11,20 +10,18 @@ $loop = Factory::create(); -$server = new Server([ - new Callback(function (ServerRequestInterface $request) use ($loop) { - return new Promise(function ($resolve, $reject) use ($request, $loop) { - $loop->addTimer(1.5, function() use ($loop, $resolve) { - $response = new Response( - 200, - array('Content-Type' => 'text/plain'), - "Hello world" - ); - $resolve($response); - }); +$server = new Server(function (ServerRequestInterface $request) use ($loop) { + return new Promise(function ($resolve, $reject) use ($request, $loop) { + $loop->addTimer(1.5, function() use ($loop, $resolve) { + $response = new Response( + 200, + array('Content-Type' => 'text/plain'), + "Hello world" + ); + $resolve($response); }); - }) -]); + }); +}); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/07-error-handling.php b/examples/07-error-handling.php index 03b3b049..5dbc6955 100644 --- a/examples/07-error-handling.php +++ b/examples/07-error-handling.php @@ -2,7 +2,6 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; -use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; use React\Promise\Promise; @@ -12,25 +11,23 @@ $loop = Factory::create(); $count = 0; -$server = new Server([ - new Callback(function (ServerRequestInterface $request) use (&$count) { - return new Promise(function ($resolve, $reject) use (&$count) { - $count++; - - if ($count%2 === 0) { - throw new Exception('Second call'); - } - - $response = new Response( - 200, - array('Content-Type' => 'text/plain'), - "Hello World!\n" - ); - - $resolve($response); - }); - }) -]); +$server = new Server(function (ServerRequestInterface $request) use (&$count) { + return new Promise(function ($resolve, $reject) use (&$count) { + $count++; + + if ($count%2 === 0) { + throw new Exception('Second call'); + } + + $response = new Response( + 200, + array('Content-Type' => 'text/plain'), + "Hello World!\n" + ); + + $resolve($response); + }); +}); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/08-stream-response.php b/examples/08-stream-response.php index ecc0f7f2..399e3a77 100644 --- a/examples/08-stream-response.php +++ b/examples/08-stream-response.php @@ -2,7 +2,6 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; -use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; use React\Stream\ThroughStream; @@ -11,30 +10,28 @@ $loop = Factory::create(); -$server = new Server([ - new Callback(function (ServerRequestInterface $request) use ($loop) { - if ($request->getMethod() !== 'GET' || $request->getUri()->getPath() !== '/') { - return new Response(404); - } - - $stream = new ThroughStream(); - - $timer = $loop->addPeriodicTimer(0.5, function () use ($stream) { - $stream->emit('data', array(microtime(true) . PHP_EOL)); - }); - - $loop->addTimer(5, function() use ($loop, $timer, $stream) { - $loop->cancelTimer($timer); - $stream->emit('end'); - }); - - return new Response( - 200, - array('Content-Type' => 'text/plain'), - $stream - ); - }) -]); +$server = new Server($loop,function (ServerRequestInterface $request) use ($loop) { + if ($request->getMethod() !== 'GET' || $request->getUri()->getPath() !== '/') { + return new Response(404); + } + + $stream = new ThroughStream(); + + $timer = $loop->addPeriodicTimer(0.5, function () use ($stream) { + $stream->emit('data', array(microtime(true) . PHP_EOL)); + }); + + $loop->addTimer(5, function() use ($loop, $timer, $stream) { + $loop->cancelTimer($timer); + $stream->emit('end'); + }); + + return new Response( + 200, + array('Content-Type' => 'text/plain'), + $stream + ); +}); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/09-stream-request.php b/examples/09-stream-request.php index dfe4403b..bcf5456b 100644 --- a/examples/09-stream-request.php +++ b/examples/09-stream-request.php @@ -2,7 +2,6 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; -use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; use React\Promise\Promise; @@ -11,35 +10,33 @@ $loop = Factory::create(); -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - return new Promise(function ($resolve, $reject) use ($request) { - $contentLength = 0; - $request->getBody()->on('data', function ($data) use (&$contentLength) { - $contentLength += strlen($data); - }); - - $request->getBody()->on('end', function () use ($resolve, &$contentLength){ - $response = new Response( - 200, - array('Content-Type' => 'text/plain'), - "The length of the submitted request body is: " . $contentLength - ); - $resolve($response); - }); - - // an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event - $request->getBody()->on('error', function (\Exception $exception) use ($resolve, &$contentLength) { - $response = new Response( - 400, - array('Content-Type' => 'text/plain'), - "An error occured while reading at length: " . $contentLength - ); - $resolve($response); - }); +$server = new Server(function (ServerRequestInterface $request) { + return new Promise(function ($resolve, $reject) use ($request) { + $contentLength = 0; + $request->getBody()->on('data', function ($data) use (&$contentLength) { + $contentLength += strlen($data); }); - }) -]); + + $request->getBody()->on('end', function () use ($resolve, &$contentLength){ + $response = new Response( + 200, + array('Content-Type' => 'text/plain'), + "The length of the submitted request body is: " . $contentLength + ); + $resolve($response); + }); + + // an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event + $request->getBody()->on('error', function (\Exception $exception) use ($resolve, &$contentLength) { + $response = new Response( + 400, + array('Content-Type' => 'text/plain'), + "An error occured while reading at length: " . $contentLength + ); + $resolve($response); + }); + }); +}); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/11-hello-world-https.php b/examples/11-hello-world-https.php index f34b9648..6610c3e0 100644 --- a/examples/11-hello-world-https.php +++ b/examples/11-hello-world-https.php @@ -2,7 +2,6 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; -use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; @@ -10,15 +9,13 @@ $loop = Factory::create(); -$server = new Server([ - new Callback(function (ServerRequestInterface $request) { - return new Response( - 200, - array('Content-Type' => 'text/plain'), - "Hello world!\n" - ); - }) -]); +$server = new Server(function (ServerRequestInterface $request) { + return new Response( + 200, + array('Content-Type' => 'text/plain'), + "Hello world!\n" + ); +}); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $socket = new \React\Socket\SecureServer($socket, $loop, array( diff --git a/examples/12-middleware.php b/examples/12-middleware.php index 22bc3cf9..68cb4946 100644 --- a/examples/12-middleware.php +++ b/examples/12-middleware.php @@ -13,7 +13,7 @@ $loop = Factory::create(); -$server = new Server([ +$server = new Server(array( new LimitHandlers(3), // Only handle three requests concurrently new Buffer(), // Buffer the whole request body before proceeding new Callback(function (ServerRequestInterface $request) use ($loop) { @@ -29,7 +29,7 @@ }); return $deferred->promise(); }) -]); +)); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/21-http-proxy.php b/examples/21-http-proxy.php index bb337788..250cbf7a 100644 --- a/examples/21-http-proxy.php +++ b/examples/21-http-proxy.php @@ -2,7 +2,6 @@ use Psr\Http\Message\RequestInterface; use React\EventLoop\Factory; -use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; use RingCentral\Psr7; @@ -11,34 +10,32 @@ $loop = Factory::create(); -$server = new Server([ - new Callback(function (RequestInterface $request) { - if (strpos($request->getRequestTarget(), '://') === false) { - return new Response( - 400, - array('Content-Type' => 'text/plain'), - 'This is a plain HTTP proxy' - ); - } - - // prepare outgoing client request by updating request-target and Host header - $host = (string)$request->getUri()->withScheme('')->withPath('')->withQuery(''); - $target = (string)$request->getUri()->withScheme('')->withHost('')->withPort(null); - if ($target === '') { - $target = $request->getMethod() === 'OPTIONS' ? '*' : '/'; - } - $outgoing = $request->withRequestTarget($target)->withHeader('Host', $host); - - // pseudo code only: simply dump the outgoing request as a string - // left up as an exercise: use an HTTP client to send the outgoing request - // and forward the incoming response to the original client request +$server = new Server(function (RequestInterface $request) { + if (strpos($request->getRequestTarget(), '://') === false) { return new Response( - 200, + 400, array('Content-Type' => 'text/plain'), - Psr7\str($outgoing) + 'This is a plain HTTP proxy' ); - }) -]); + } + + // prepare outgoing client request by updating request-target and Host header + $host = (string)$request->getUri()->withScheme('')->withPath('')->withQuery(''); + $target = (string)$request->getUri()->withScheme('')->withHost('')->withPort(null); + if ($target === '') { + $target = $request->getMethod() === 'OPTIONS' ? '*' : '/'; + } + $outgoing = $request->withRequestTarget($target)->withHeader('Host', $host); + + // pseudo code only: simply dump the outgoing request as a string + // left up as an exercise: use an HTTP client to send the outgoing request + // and forward the incoming response to the original client request + return new Response( + 200, + array('Content-Type' => 'text/plain'), + Psr7\str($outgoing) + ); +}); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/22-connect-proxy.php b/examples/22-connect-proxy.php index dfca55e0..ed8e80b0 100644 --- a/examples/22-connect-proxy.php +++ b/examples/22-connect-proxy.php @@ -2,47 +2,44 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; -use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; -use React\Socket\ConnectionInterface; use React\Socket\Connector; +use React\Socket\ConnectionInterface; require __DIR__ . '/../vendor/autoload.php'; $loop = Factory::create(); $connector = new Connector($loop); -$server = new Server([ - new Callback(function (ServerRequestInterface $request) use ($connector) { - if ($request->getMethod() !== 'CONNECT') { +$server = new Server(function (ServerRequestInterface $request) use ($connector) { + if ($request->getMethod() !== 'CONNECT') { + return new Response( + 405, + array('Content-Type' => 'text/plain', 'Allow' => 'CONNECT'), + 'This is a HTTP CONNECT (secure HTTPS) proxy' + ); + } + + // try to connect to given target host + return $connector->connect($request->getRequestTarget())->then( + function (ConnectionInterface $remote) { + // connection established => forward data return new Response( - 405, - array('Content-Type' => 'text/plain', 'Allow' => 'CONNECT'), - 'This is a HTTP CONNECT (secure HTTPS) proxy' + 200, + array(), + $remote + ); + }, + function ($e) { + return new Response( + 502, + array('Content-Type' => 'text/plain'), + 'Unable to connect: ' . $e->getMessage() ); } - - // try to connect to given target host - return $connector->connect($request->getRequestTarget())->then( - function (ConnectionInterface $remote) { - // connection established => forward data - return new Response( - 200, - array(), - $remote - ); - }, - function ($e) { - return new Response( - 502, - array('Content-Type' => 'text/plain'), - 'Unable to connect: ' . $e->getMessage() - ); - } - ); - }) -]); + ); +}); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/31-upgrade-echo.php b/examples/31-upgrade-echo.php index 2fbd2803..b098ef03 100644 --- a/examples/31-upgrade-echo.php +++ b/examples/31-upgrade-echo.php @@ -19,7 +19,6 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; -use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; use React\Stream\ThroughStream; @@ -28,30 +27,28 @@ $loop = Factory::create(); -$server = new Server([ - new Callback(function (ServerRequestInterface $request) use ($loop) { - if ($request->getHeaderLine('Upgrade') !== 'echo' || $request->getProtocolVersion() === '1.0') { - return new Response(426, array('Upgrade' => 'echo'), '"Upgrade: echo" required'); - } - - // simply return a duplex ThroughStream here - // it will simply emit any data that is sent to it - // this means that any Upgraded data will simply be sent back to the client - $stream = new ThroughStream(); - - $loop->addTimer(0, function () use ($stream) { - $stream->write("Hello! Anything you send will be piped back." . PHP_EOL); - }); - - return new Response( - 101, - array( - 'Upgrade' => 'echo' - ), - $stream - ); - }) -]); +$server = new Server(function (ServerRequestInterface $request) use ($loop) { + if ($request->getHeaderLine('Upgrade') !== 'echo' || $request->getProtocolVersion() === '1.0') { + return new Response(426, array('Upgrade' => 'echo'), '"Upgrade: echo" required'); + } + + // simply return a duplex ThroughStream here + // it will simply emit any data that is sent to it + // this means that any Upgraded data will simply be sent back to the client + $stream = new ThroughStream(); + + $loop->addTimer(0, function () use ($stream) { + $stream->write("Hello! Anything you send will be piped back." . PHP_EOL); + }); + + return new Response( + 101, + array( + 'Upgrade' => 'echo' + ), + $stream + ); +}); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/32-upgrade-chat.php b/examples/32-upgrade-chat.php index be9b1d07..49cb0305 100644 --- a/examples/32-upgrade-chat.php +++ b/examples/32-upgrade-chat.php @@ -21,7 +21,6 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; -use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; use React\Stream\CompositeStream; @@ -36,50 +35,48 @@ // this means that any Upgraded data will simply be sent back to the client $chat = new ThroughStream(); -$server = new Server([ - new Callback(function (ServerRequestInterface $request) use ($loop, $chat) { - if ($request->getHeaderLine('Upgrade') !== 'chat' || $request->getProtocolVersion() === '1.0') { - return new Response(426, array('Upgrade' => 'chat'), '"Upgrade: chat" required'); - } - - // user stream forwards chat data and accepts incoming data - $out = $chat->pipe(new ThroughStream()); - $in = new ThroughStream(); - $stream = new CompositeStream( - $out, - $in - ); - - // assign some name for this new connection - $username = 'user' . mt_rand(); - - // send anything that is received to the whole channel - $in->on('data', function ($data) use ($username, $chat) { - $data = trim(preg_replace('/[^\w\d \.\,\-\!\?]/u', '', $data)); - - $chat->write($username . ': ' . $data . PHP_EOL); - }); - - // say hello to new user - $loop->addTimer(0, function () use ($chat, $username, $out) { - $out->write('Welcome to this chat example, ' . $username . '!' . PHP_EOL); - $chat->write($username . ' joined' . PHP_EOL); - }); - - // send goodbye to channel once connection closes - $stream->on('close', function () use ($username, $chat) { - $chat->write($username . ' left' . PHP_EOL); - }); - - return new Response( - 101, - array( - 'Upgrade' => 'chat' - ), - $stream - ); - }) -]); +$server = new Server(function (ServerRequestInterface $request) use ($loop, $chat) { + if ($request->getHeaderLine('Upgrade') !== 'chat' || $request->getProtocolVersion() === '1.0') { + return new Response(426, array('Upgrade' => 'chat'), '"Upgrade: chat" required'); + } + + // user stream forwards chat data and accepts incoming data + $out = $chat->pipe(new ThroughStream()); + $in = new ThroughStream(); + $stream = new CompositeStream( + $out, + $in + ); + + // assign some name for this new connection + $username = 'user' . mt_rand(); + + // send anything that is received to the whole channel + $in->on('data', function ($data) use ($username, $chat) { + $data = trim(preg_replace('/[^\w\d \.\,\-\!\?]/u', '', $data)); + + $chat->write($username . ': ' . $data . PHP_EOL); + }); + + // say hello to new user + $loop->addTimer(0, function () use ($chat, $username, $out) { + $out->write('Welcome to this chat example, ' . $username . '!' . PHP_EOL); + $chat->write($username . ' joined' . PHP_EOL); + }); + + // send goodbye to channel once connection closes + $stream->on('close', function () use ($username, $chat) { + $chat->write($username . ' left' . PHP_EOL); + }); + + return new Response( + 101, + array( + 'Upgrade' => 'chat' + ), + $stream + ); +}); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/examples/99-benchmark-download.php b/examples/99-benchmark-download.php index ffac4697..a8a6e03a 100644 --- a/examples/99-benchmark-download.php +++ b/examples/99-benchmark-download.php @@ -9,7 +9,6 @@ use Evenement\EventEmitter; use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; -use React\Http\Middleware\Callback; use React\Http\Response; use React\Http\Server; use React\Stream\ReadableStreamInterface; @@ -87,34 +86,32 @@ public function getSize() } } -$server = new Server([ - new Callback(function (ServerRequestInterface $request) use ($loop) { - switch ($request->getUri()->getPath()) { - case '/': - return new Response( - 200, - array('Content-Type' => 'text/html'), - '1g.bin
10g.bin' - ); - case '/1g.bin': - $stream = new ChunkRepeater(str_repeat('.', 1000000), 1000); - break; - case '/10g.bin': - $stream = new ChunkRepeater(str_repeat('.', 1000000), 10000); - break; - default: - return new Response(404); - } +$server = new Server(function (ServerRequestInterface $request) use ($loop) { + switch ($request->getUri()->getPath()) { + case '/': + return new Response( + 200, + array('Content-Type' => 'text/html'), + '1g.bin
10g.bin' + ); + case '/1g.bin': + $stream = new ChunkRepeater(str_repeat('.', 1000000), 1000); + break; + case '/10g.bin': + $stream = new ChunkRepeater(str_repeat('.', 1000000), 10000); + break; + default: + return new Response(404); + } - $loop->addTimer(0, array($stream, 'resume')); + $loop->addTimer(0, array($stream, 'resume')); - return new Response( - 200, - array('Content-Type' => 'application/octet-data', 'Content-Length' => $stream->getSize()), - $stream - ); - }) -]); + return new Response( + 200, + array('Content-Type' => 'application/octet-data', 'Content-Length' => $stream->getSize()), + $stream + ); +}); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); $server->listen($socket); diff --git a/src/Server.php b/src/Server.php index e96691eb..16532082 100644 --- a/src/Server.php +++ b/src/Server.php @@ -3,6 +3,7 @@ namespace React\Http; use Evenement\EventEmitter; +use React\Http\Middleware\Callback; use React\Socket\ServerInterface; use React\Socket\ConnectionInterface; use Psr\Http\Message\RequestInterface; @@ -93,11 +94,17 @@ class Server extends EventEmitter * connections in order to then parse incoming data as HTTP. * See also [listen()](#listen) for more details. * - * @param MiddlewareInterface[]|MiddlewareStackInterface $middlewares + * @param callable|MiddlewareInterface[]|MiddlewareStackInterface $middlewares * @see self::listen() */ public function __construct($middlewares) { + if (is_callable($middlewares)) { + $middlewares = array( + new Callback($middlewares), + ); + } + if (is_array($middlewares)) { $this->middlewareStack = new MiddlewareStack( new Response(404), @@ -111,7 +118,7 @@ public function __construct($middlewares) return; } - throw new \InvalidArgumentException('Only MiddlewareInterface[] or MiddlewareStackInterface implementations are supported'); + throw new \InvalidArgumentException('Only callables, MiddlewareInterface[], or MiddlewareStackInterface implementations are supported'); } /** diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 7fb14ae9..88d5e54b 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -2,7 +2,6 @@ namespace React\Tests\Http; -use React\Http\Middleware\Callback; use React\Http\Server; use Psr\Http\Message\ServerRequestInterface; use React\Http\Response; @@ -42,9 +41,7 @@ public function setUp() public function testRequestEventWillNotBeEmittedForIncompleteHeaders() { - $server = new Server(array( - new Callback($this->expectCallableNever()) - )); + $server = new Server($this->expectCallableNever()); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -56,11 +53,9 @@ public function testRequestEventWillNotBeEmittedForIncompleteHeaders() public function testRequestEventIsEmitted() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return \React\Promise\resolve(new Response()); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return \React\Promise\resolve(new Response()); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -73,14 +68,12 @@ public function testRequestEvent() { $i = 0; $requestAssertion = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$i, &$requestAssertion) { - $i++; - $requestAssertion = $request; + $server = new Server(function (ServerRequestInterface $request) use (&$i, &$requestAssertion) { + $i++; + $requestAssertion = $request; - return \React\Promise\resolve(new Response()); - }) - )); + return \React\Promise\resolve(new Response()); + }); $this->connection ->expects($this->any()) @@ -109,12 +102,10 @@ public function testRequestEvent() public function testRequestGetWithHostAndCustomPort() { $requestAssertion = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -134,12 +125,10 @@ public function testRequestGetWithHostAndCustomPort() public function testRequestGetWithHostAndHttpsPort() { $requestAssertion = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -159,12 +148,10 @@ public function testRequestGetWithHostAndHttpsPort() public function testRequestGetWithHostAndDefaultPortWillBeIgnored() { $requestAssertion = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -184,12 +171,10 @@ public function testRequestGetWithHostAndDefaultPortWillBeIgnored() public function testRequestOptionsAsterisk() { $requestAssertion = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -207,9 +192,7 @@ public function testRequestOptionsAsterisk() public function testRequestNonOptionsWithAsteriskRequestTargetWillReject() { - $server = new Server(array( - new Callback($this->expectCallableNever()) - )); + $server = new Server($this->expectCallableNever()); $server->on('error', $this->expectCallableOnce()); $server->listen($this->socket); @@ -222,12 +205,10 @@ public function testRequestNonOptionsWithAsteriskRequestTargetWillReject() public function testRequestConnectAuthorityForm() { $requestAssertion = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -247,12 +228,10 @@ public function testRequestConnectAuthorityForm() public function testRequestConnectWithoutHostWillBeAdded() { $requestAssertion = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -272,12 +251,10 @@ public function testRequestConnectWithoutHostWillBeAdded() public function testRequestConnectAuthorityFormWithDefaultPortWillBeIgnored() { $requestAssertion = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -297,12 +274,10 @@ public function testRequestConnectAuthorityFormWithDefaultPortWillBeIgnored() public function testRequestConnectAuthorityFormNonMatchingHostWillBeOverwritten() { $requestAssertion = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -321,9 +296,7 @@ public function testRequestConnectAuthorityFormNonMatchingHostWillBeOverwritten( public function testRequestConnectOriginFormRequestTargetWillReject() { - $server = new Server(array( - new Callback($this->expectCallableNever()) - )); + $server = new Server($this->expectCallableNever()); $server->on('error', $this->expectCallableOnce()); $server->listen($this->socket); @@ -335,9 +308,7 @@ public function testRequestConnectOriginFormRequestTargetWillReject() public function testRequestNonConnectWithAuthorityRequestTargetWillReject() { - $server = new Server(array( - new Callback($this->expectCallableNever()) - )); + $server = new Server($this->expectCallableNever()); $server->on('error', $this->expectCallableOnce()); $server->listen($this->socket); @@ -351,12 +322,10 @@ public function testRequestWithoutHostEventUsesSocketAddress() { $requestAssertion = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }); $this->connection ->expects($this->any()) @@ -380,12 +349,10 @@ public function testRequestAbsoluteEvent() { $requestAssertion = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -405,12 +372,10 @@ public function testRequestAbsoluteAddsMissingHostEvent() { $requestAssertion = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }); $server->on('error', 'printf'); $server->listen($this->socket); @@ -431,12 +396,10 @@ public function testRequestAbsoluteNonMatchingHostWillBeOverwritten() { $requestAssertion = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -456,12 +419,10 @@ public function testRequestOptionsAsteriskEvent() { $requestAssertion = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -481,12 +442,10 @@ public function testRequestOptionsAbsoluteEvent() { $requestAssertion = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestAssertion) { - $requestAssertion = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestAssertion) { + $requestAssertion = $request; + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -504,12 +463,10 @@ public function testRequestOptionsAbsoluteEvent() public function testRequestPauseWillbeForwardedToConnection() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - $request->getBody()->pause(); - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + $request->getBody()->pause(); + return new Response(); + }); $this->connection->expects($this->once())->method('pause'); @@ -527,12 +484,10 @@ public function testRequestPauseWillbeForwardedToConnection() public function testRequestResumeWillbeForwardedToConnection() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - $request->getBody()->resume(); - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + $request->getBody()->resume(); + return new Response(); + }); $this->connection->expects($this->once())->method('resume'); @@ -545,12 +500,10 @@ public function testRequestResumeWillbeForwardedToConnection() public function testRequestCloseWillPauseConnection() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - $request->getBody()->close(); - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + $request->getBody()->close(); + return new Response(); + }); $this->connection->expects($this->once())->method('pause'); @@ -563,14 +516,12 @@ public function testRequestCloseWillPauseConnection() public function testRequestPauseAfterCloseWillNotBeForwarded() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - $request->getBody()->close(); - $request->getBody()->pause();# + $server = new Server(function (ServerRequestInterface $request) { + $request->getBody()->close(); + $request->getBody()->pause();# - return new Response(); - }) - )); + return new Response(); + }); $this->connection->expects($this->once())->method('pause'); @@ -583,14 +534,12 @@ public function testRequestPauseAfterCloseWillNotBeForwarded() public function testRequestResumeAfterCloseWillNotBeForwarded() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - $request->getBody()->close(); - $request->getBody()->resume(); + $server = new Server(function (ServerRequestInterface $request) { + $request->getBody()->close(); + $request->getBody()->resume(); - return new Response(); - }) - )); + return new Response(); + }); $this->connection->expects($this->once())->method('pause'); $this->connection->expects($this->never())->method('resume'); @@ -606,13 +555,11 @@ public function testRequestEventWithoutBodyWillNotEmitData() { $never = $this->expectCallableNever(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($never) { - $request->getBody()->on('data', $never); + $server = new Server(function (ServerRequestInterface $request) use ($never) { + $request->getBody()->on('data', $never); - return new Response(); - }) - )); + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -625,13 +572,11 @@ public function testRequestEventWithSecondDataEventWillEmitBodyData() { $once = $this->expectCallableOnceWith('incomplete'); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($once) { - $request->getBody()->on('data', $once); + $server = new Server(function (ServerRequestInterface $request) use ($once) { + $request->getBody()->on('data', $once); - return new Response(); - }) - )); + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -649,13 +594,11 @@ public function testRequestEventWithPartialBodyWillEmitData() { $once = $this->expectCallableOnceWith('incomplete'); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($once) { - $request->getBody()->on('data', $once); + $server = new Server(function (ServerRequestInterface $request) use ($once) { + $request->getBody()->on('data', $once); - return new Response(); - }) - )); + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -674,11 +617,9 @@ public function testRequestEventWithPartialBodyWillEmitData() public function testResponseContainsPoweredByHeader() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return new Response(); + }); $buffer = ''; @@ -706,11 +647,9 @@ public function testPendingPromiseWillNotSendAnything() { $never = $this->expectCallableNever(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($never) { - return new Promise(function () { }, $never); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use ($never) { + return new Promise(function () { }, $never); + }); $buffer = ''; @@ -738,11 +677,9 @@ public function testPendingPromiseWillBeCancelledIfConnectionCloses() { $once = $this->expectCallableOnce(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($once) { - return new Promise(function () { }, $once); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use ($once) { + return new Promise(function () { }, $once); + }); $buffer = ''; @@ -772,11 +709,9 @@ public function testStreamAlreadyClosedWillSendEmptyBodyChunkedEncoded() $stream = new ThroughStream(); $stream->close(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }); $buffer = ''; @@ -805,11 +740,9 @@ public function testResponseStreamEndingWillSendEmptyBodyChunkedEncoded() { $stream = new ThroughStream(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }); $buffer = ''; @@ -841,11 +774,9 @@ public function testResponseStreamAlreadyClosedWillSendEmptyBodyPlainHttp10() $stream = new ThroughStream(); $stream->close(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }); $buffer = ''; @@ -875,11 +806,9 @@ public function testResponseStreamWillBeClosedIfConnectionIsAlreadyClosed() $stream = new ThroughStream(); $stream->on('close', $this->expectCallableOnce()); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }); $buffer = ''; @@ -928,11 +857,9 @@ public function testResponseStreamWillBeClosedIfConnectionEmitsCloseEvent() $stream = new ThroughStream(); $stream->on('close', $this->expectCallableOnce()); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -944,11 +871,9 @@ public function testResponseStreamWillBeClosedIfConnectionEmitsCloseEvent() public function testUpgradeInResponseCanBeUsedToAdvertisePossibleUpgrade() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return new Response(200, array('date' => '', 'x-powered-by' => '', 'Upgrade' => 'demo'), 'foo'); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return new Response(200, array('date' => '', 'x-powered-by' => '', 'Upgrade' => 'demo'), 'foo'); + }); $buffer = ''; @@ -974,11 +899,9 @@ function ($data) use (&$buffer) { public function testUpgradeWishInRequestCanBeIgnoredByReturningNormalResponse() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return new Response(200, array('date' => '', 'x-powered-by' => ''), 'foo'); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return new Response(200, array('date' => '', 'x-powered-by' => ''), 'foo'); + }); $buffer = ''; @@ -1004,11 +927,9 @@ function ($data) use (&$buffer) { public function testUpgradeSwitchingProtocolIncludesConnectionUpgradeHeaderWithoutContentLength() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return new Response(101, array('date' => '', 'x-powered-by' => '', 'Upgrade' => 'demo'), 'foo'); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return new Response(101, array('date' => '', 'x-powered-by' => '', 'Upgrade' => 'demo'), 'foo'); + }); $server->on('error', 'printf'); @@ -1038,11 +959,9 @@ public function testUpgradeSwitchingProtocolWithStreamWillPipeDataToConnection() { $stream = new ThroughStream(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($stream) { - return new Response(101, array('date' => '', 'x-powered-by' => '', 'Upgrade' => 'demo'), $stream); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use ($stream) { + return new Response(101, array('date' => '', 'x-powered-by' => '', 'Upgrade' => 'demo'), $stream); + }); $buffer = ''; @@ -1073,11 +992,9 @@ public function testConnectResponseStreamWillPipeDataToConnection() { $stream = new ThroughStream(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }); $buffer = ''; @@ -1109,11 +1026,9 @@ public function testConnectResponseStreamWillPipeDataFromConnection() { $stream = new ThroughStream(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1126,12 +1041,10 @@ public function testConnectResponseStreamWillPipeDataFromConnection() public function testResponseContainsSameRequestProtocolVersionAndChunkedBodyForHttp11() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - $response = new Response(200, array(), 'bye'); - return \React\Promise\resolve($response); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + $response = new Response(200, array(), 'bye'); + return \React\Promise\resolve($response); + }); $buffer = ''; @@ -1158,12 +1071,10 @@ function ($data) use (&$buffer) { public function testResponseContainsSameRequestProtocolVersionAndRawBodyForHttp10() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - $response = new Response(200, array(), 'bye'); - return \React\Promise\resolve($response); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + $response = new Response(200, array(), 'bye'); + return \React\Promise\resolve($response); + }); $buffer = ''; @@ -1191,11 +1102,9 @@ function ($data) use (&$buffer) { public function testResponseContainsNoResponseBodyForHeadRequest() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return new Response(200, array(), 'bye'); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return new Response(200, array(), 'bye'); + }); $buffer = ''; $this->connection @@ -1221,11 +1130,9 @@ function ($data) use (&$buffer) { public function testResponseContainsNoResponseBodyAndNoContentLengthForNoContentStatus() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return new Response(204, array(), 'bye'); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return new Response(204, array(), 'bye'); + }); $buffer = ''; $this->connection @@ -1252,11 +1159,9 @@ function ($data) use (&$buffer) { public function testResponseContainsNoResponseBodyForNotModifiedStatus() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return new Response(304, array(), 'bye'); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return new Response(304, array(), 'bye'); + }); $buffer = ''; $this->connection @@ -1284,9 +1189,7 @@ function ($data) use (&$buffer) { public function testRequestInvalidHttpProtocolVersionWillEmitErrorAndSendErrorResponse() { $error = null; - $server = new Server(array( - new Callback($this->expectCallableNever()) - )); + $server = new Server($this->expectCallableNever()); $server->on('error', function ($message) use (&$error) { $error = $message; }); @@ -1320,9 +1223,7 @@ function ($data) use (&$buffer) { public function testRequestOverflowWillEmitErrorAndSendErrorResponse() { $error = null; - $server = new Server(array( - new Callback($this->expectCallableNever()) - )); + $server = new Server($this->expectCallableNever()); $server->on('error', function ($message) use (&$error) { $error = $message; }); @@ -1356,9 +1257,7 @@ function ($data) use (&$buffer) { public function testRequestInvalidWillEmitErrorAndSendErrorResponse() { $error = null; - $server = new Server(array( - new Callback($this->expectCallableNever()) - )); + $server = new Server($this->expectCallableNever()); $server->on('error', function ($message) use (&$error) { $error = $message; }); @@ -1395,16 +1294,14 @@ public function testBodyDataWillBeSendViaRequestEvent() $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }) - )); + return \React\Promise\resolve(new Response()); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1427,17 +1324,15 @@ public function testChunkedEncodedRequestWillBeParsedForRequestEvent() $errorEvent = $this->expectCallableNever(); $requestValidation = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent, &$requestValidation) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); - $requestValidation = $request; + $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent, &$requestValidation) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); + $requestValidation = $request; - return \React\Promise\resolve(new Response()); - }) - )); + return \React\Promise\resolve(new Response()); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1462,16 +1357,14 @@ public function testChunkedEncodedRequestAdditionalDataWontBeEmitted() $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }) - )); + return \React\Promise\resolve(new Response()); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1495,16 +1388,14 @@ public function testEmptyChunkedEncodedRequest() $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }) - )); + return \React\Promise\resolve(new Response()); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1526,16 +1417,14 @@ public function testChunkedIsUpperCase() $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }) - )); + return \React\Promise\resolve(new Response()); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1558,16 +1447,14 @@ public function testChunkedIsMixedUpperAndLowerCase() $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }) - )); + return \React\Promise\resolve(new Response()); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1585,9 +1472,7 @@ public function testChunkedIsMixedUpperAndLowerCase() public function testRequestWithMalformedHostWillEmitErrorAndSendErrorResponse() { $error = null; - $server = new Server(array( - new Callback($this->expectCallableNever()) - )); + $server = new Server($this->expectCallableNever()); $server->on('error', function ($message) use (&$error) { $error = $message; }); @@ -1620,9 +1505,7 @@ function ($data) use (&$buffer) { public function testRequestWithInvalidHostUriComponentsWillEmitErrorAndSendErrorResponse() { $error = null; - $server = new Server(array( - new Callback($this->expectCallableNever()) - )); + $server = new Server($this->expectCallableNever()); $server->on('error', function ($message) use (&$error) { $error = $message; }); @@ -1659,16 +1542,14 @@ public function testWontEmitFurtherDataWhenContentLengthIsReached() $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }) - )); + return \React\Promise\resolve(new Response()); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1692,16 +1573,14 @@ public function testWontEmitFurtherDataWhenContentLengthIsReachedSplitted() $errorEvent = $this->expectCallableNever(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }) - )); + return \React\Promise\resolve(new Response()); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1728,16 +1607,14 @@ public function testContentLengthContainsZeroWillEmitEndEvent() $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }) - )); + return \React\Promise\resolve(new Response()); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1758,16 +1635,14 @@ public function testContentLengthContainsZeroWillEmitEndEventAdditionalDataWillB $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }) - )); + return \React\Promise\resolve(new Response()); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1789,16 +1664,14 @@ public function testContentLengthContainsZeroWillEmitEndEventAdditionalDataWillB $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }) - )); + return \React\Promise\resolve(new Response()); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1824,17 +1697,15 @@ public function testContentLengthWillBeIgnoredIfTransferEncodingIsSet() $errorEvent = $this->expectCallableNever(); $requestValidation = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent, &$requestValidation) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); - $requestValidation = $request; - - return \React\Promise\resolve(new Response()); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent, &$requestValidation) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); + $requestValidation = $request; + + return \React\Promise\resolve(new Response()); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1865,17 +1736,15 @@ public function testInvalidContentLengthWillBeIgnoreddIfTransferEncodingIsSet() $errorEvent = $this->expectCallableNever(); $requestValidation = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent, &$requestValidation) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); - $requestValidation = $request; - - return \React\Promise\resolve(new Response()); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent, &$requestValidation) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); + $requestValidation = $request; + + return \React\Promise\resolve(new Response()); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -1902,9 +1771,7 @@ public function testInvalidContentLengthWillBeIgnoreddIfTransferEncodingIsSet() public function testNonIntegerContentLengthValueWillLeadToError() { $error = null; - $server = new Server(array( - new Callback($this->expectCallableNever()) - )); + $server = new Server($this->expectCallableNever()); $server->on('error', function ($message) use (&$error) { $error = $message; }); @@ -1941,9 +1808,7 @@ function ($data) use (&$buffer) { public function testNonIntegerContentLengthValueWillLeadToErrorWithNoBodyForHeadRequest() { $error = null; - $server = new Server(array( - new Callback($this->expectCallableNever()) - )); + $server = new Server($this->expectCallableNever()); $server->on('error', function ($message) use (&$error) { $error = $message; }); @@ -1980,9 +1845,7 @@ function ($data) use (&$buffer) { public function testMultipleIntegerInContentLengthWillLeadToError() { $error = null; - $server = new Server(array( - new Callback($this->expectCallableNever()) - )); + $server = new Server($this->expectCallableNever()); $server->on('error', function ($message) use (&$error) { $error = $message; }); @@ -2019,12 +1882,10 @@ function ($data) use (&$buffer) { public function testInvalidChunkHeaderResultsInErrorOnRequestStream() { $errorEvent = $this->expectCallableOnceWith($this->isInstanceOf('Exception')); - $server = new Server(array( - new Callback(function ($request) use ($errorEvent){ - $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }) - )); + $server = new Server(function ($request) use ($errorEvent){ + $request->getBody()->on('error', $errorEvent); + return \React\Promise\resolve(new Response()); + }); $this->connection->expects($this->never())->method('close'); $this->connection->expects($this->once())->method('pause'); @@ -2045,12 +1906,10 @@ public function testInvalidChunkHeaderResultsInErrorOnRequestStream() public function testTooLongChunkHeaderResultsInErrorOnRequestStream() { $errorEvent = $this->expectCallableOnceWith($this->isInstanceOf('Exception')); - $server = new Server(array( - new Callback(function ($request) use ($errorEvent){ - $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }) - )); + $server = new Server(function ($request) use ($errorEvent){ + $request->getBody()->on('error', $errorEvent); + return \React\Promise\resolve(new Response()); + }); $this->connection->expects($this->never())->method('close'); $this->connection->expects($this->once())->method('pause'); @@ -2073,12 +1932,10 @@ public function testTooLongChunkHeaderResultsInErrorOnRequestStream() public function testTooLongChunkBodyResultsInErrorOnRequestStream() { $errorEvent = $this->expectCallableOnceWith($this->isInstanceOf('Exception')); - $server = new Server(array( - new Callback(function ($request) use ($errorEvent){ - $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }) - )); + $server = new Server(function ($request) use ($errorEvent){ + $request->getBody()->on('error', $errorEvent); + return \React\Promise\resolve(new Response()); + }); $this->connection->expects($this->never())->method('close'); $this->connection->expects($this->once())->method('pause'); @@ -2099,12 +1956,10 @@ public function testTooLongChunkBodyResultsInErrorOnRequestStream() public function testUnexpectedEndOfConnectionWillResultsInErrorOnRequestStream() { $errorEvent = $this->expectCallableOnceWith($this->isInstanceOf('Exception')); - $server = new Server(array( - new Callback(function ($request) use ($errorEvent){ - $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }) - )); + $server = new Server(function ($request) use ($errorEvent){ + $request->getBody()->on('error', $errorEvent); + return \React\Promise\resolve(new Response()); + }); $this->connection->expects($this->never())->method('close'); $this->connection->expects($this->once())->method('pause'); @@ -2125,11 +1980,9 @@ public function testUnexpectedEndOfConnectionWillResultsInErrorOnRequestStream() public function testErrorInChunkedDecoderNeverClosesConnection() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return \React\Promise\resolve(new Response()); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return \React\Promise\resolve(new Response()); + }); $this->connection->expects($this->never())->method('close'); $this->connection->expects($this->once())->method('pause'); @@ -2149,11 +2002,9 @@ public function testErrorInChunkedDecoderNeverClosesConnection() public function testErrorInLengthLimitedStreamNeverClosesConnection() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return \React\Promise\resolve(new Response()); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return \React\Promise\resolve(new Response()); + }); $this->connection->expects($this->never())->method('close'); $this->connection->expects($this->once())->method('pause'); @@ -2174,12 +2025,10 @@ public function testErrorInLengthLimitedStreamNeverClosesConnection() public function testCloseRequestWillPauseConnection() { - $server = new Server(array( - new Callback(function ($request) { - $request->getBody()->close(); - return \React\Promise\resolve(new Response()); - }) - )); + $server = new Server(function ($request) { + $request->getBody()->close(); + return \React\Promise\resolve(new Response()); + }); $this->connection->expects($this->never())->method('close'); $this->connection->expects($this->once())->method('pause'); @@ -2198,16 +2047,14 @@ public function testEndEventWillBeEmittedOnSimpleRequest() $endEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(array( - new Callback(function ($request) use ($dataEvent, $closeEvent, $endEvent, $errorEvent){ - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(function ($request) use ($dataEvent, $closeEvent, $endEvent, $errorEvent){ + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }) - )); + return \React\Promise\resolve(new Response()); + }); $this->connection->expects($this->once())->method('pause'); $this->connection->expects($this->never())->method('close'); @@ -2227,16 +2074,14 @@ public function testRequestWithoutDefinedLengthWillIgnoreDataEvent() $closeEvent = $this->expectCallableOnce(); $errorEvent = $this->expectCallableNever(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { - $request->getBody()->on('data', $dataEvent); - $request->getBody()->on('end', $endEvent); - $request->getBody()->on('close', $closeEvent); - $request->getBody()->on('error', $errorEvent); + $server = new Server(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent) { + $request->getBody()->on('data', $dataEvent); + $request->getBody()->on('end', $endEvent); + $request->getBody()->on('close', $closeEvent); + $request->getBody()->on('error', $errorEvent); - return \React\Promise\resolve(new Response()); - }) - )); + return \React\Promise\resolve(new Response()); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -2250,12 +2095,10 @@ public function testRequestWithoutDefinedLengthWillIgnoreDataEvent() public function testResponseWillBeChunkDecodedByDefault() { $stream = new ThroughStream(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($stream) { - $response = new Response(200, array(), $stream); - return \React\Promise\resolve($response); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use ($stream) { + $response = new Response(200, array(), $stream); + return \React\Promise\resolve($response); + }); $buffer = ''; $this->connection @@ -2283,20 +2126,18 @@ function ($data) use (&$buffer) { public function testContentLengthWillBeRemovedForResponseStream() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - $response = new Response( - 200, - array( - 'Content-Length' => 5, - 'Transfer-Encoding' => 'chunked' - ), - 'hello' - ); - - return \React\Promise\resolve($response); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + $response = new Response( + 200, + array( + 'Content-Length' => 5, + 'Transfer-Encoding' => 'chunked' + ), + 'hello' + ); + + return \React\Promise\resolve($response); + }); $buffer = ''; $this->connection @@ -2325,19 +2166,17 @@ function ($data) use (&$buffer) { public function testOnlyAllowChunkedEncoding() { $stream = new ThroughStream(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($stream) { - $response = new Response( - 200, - array( - 'Transfer-Encoding' => 'custom' - ), - $stream - ); - - return \React\Promise\resolve($response); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use ($stream) { + $response = new Response( + 200, + array( + 'Transfer-Encoding' => 'custom' + ), + $stream + ); + + return \React\Promise\resolve($response); + }); $buffer = ''; $this->connection @@ -2366,11 +2205,9 @@ function ($data) use (&$buffer) { public function testDateHeaderWillBeAddedWhenNoneIsGiven() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return \React\Promise\resolve(new Response()); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return \React\Promise\resolve(new Response()); + }); $buffer = ''; $this->connection @@ -2398,12 +2235,10 @@ function ($data) use (&$buffer) { public function testAddCustomDateHeader() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - $response = new Response(200, array("Date" => "Tue, 15 Nov 1994 08:12:31 GMT")); - return \React\Promise\resolve($response); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + $response = new Response(200, array("Date" => "Tue, 15 Nov 1994 08:12:31 GMT")); + return \React\Promise\resolve($response); + }); $buffer = ''; $this->connection @@ -2431,12 +2266,10 @@ function ($data) use (&$buffer) { public function testRemoveDateHeader() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - $response = new Response(200, array('Date' => '')); - return \React\Promise\resolve($response); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + $response = new Response(200, array('Date' => '')); + return \React\Promise\resolve($response); + }); $buffer = ''; $this->connection @@ -2466,9 +2299,7 @@ public function testOnlyChunkedEncodingIsAllowedForTransferEncoding() { $error = null; - $server = new Server(array( - new Callback($this->expectCallableNever()) - )); + $server = new Server($this->expectCallableNever()); $server->on('error', function ($exception) use (&$error) { $error = $exception; }); @@ -2505,9 +2336,7 @@ public function testOnlyChunkedEncodingIsAllowedForTransferEncodingWithHttp10() { $error = null; - $server = new Server(array( - new Callback($this->expectCallableNever()) - )); + $server = new Server($this->expectCallableNever()); $server->on('error', function ($exception) use (&$error) { $error = $exception; }); @@ -2540,11 +2369,9 @@ function ($data) use (&$buffer) { public function test100ContinueRequestWillBeHandled() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return \React\Promise\resolve(new Response()); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return \React\Promise\resolve(new Response()); + }); $buffer = ''; $this->connection @@ -2574,11 +2401,9 @@ function ($data) use (&$buffer) { public function testContinueWontBeSendForHttp10() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return \React\Promise\resolve(new Response()); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return \React\Promise\resolve(new Response()); + }); $buffer = ''; $this->connection @@ -2606,11 +2431,9 @@ function ($data) use (&$buffer) { public function testContinueWithLaterResponse() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return \React\Promise\resolve(new Response()); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return \React\Promise\resolve(new Response()); + }); $buffer = ''; @@ -2640,16 +2463,22 @@ function ($data) use (&$buffer) { $this->assertContains("HTTP/1.1 200 OK\r\n", $buffer); } + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidCallbackFunctionLeadsToException() + { + $server = new Server('invalid'); + } + public function testHttpBodyStreamAsBodyWillStreamData() { $input = new ThroughStream(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($input) { - $response = new Response(200, array(), $input); - return \React\Promise\resolve($response); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use ($input) { + $response = new Response(200, array(), $input); + return \React\Promise\resolve($response); + }); $buffer = ''; $this->connection @@ -2682,12 +2511,10 @@ public function testHttpBodyStreamWithContentLengthWillStreamTillLength() { $input = new ThroughStream(); - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use ($input) { - $response = new Response(200, array('Content-Length' => 5), $input); - return \React\Promise\resolve($response); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use ($input) { + $response = new Response(200, array('Content-Length' => 5), $input); + return \React\Promise\resolve($response); + }); $buffer = ''; $this->connection @@ -2719,11 +2546,9 @@ function ($data) use (&$buffer) { public function testCallbackFunctionReturnsPromise() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return \React\Promise\resolve(new Response()); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return \React\Promise\resolve(new Response()); + }); $buffer = ''; $this->connection @@ -2749,11 +2574,9 @@ function ($data) use (&$buffer) { public function testReturnInvalidTypeWillResultInError() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return "invalid"; - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return "invalid"; + }); $exception = null; $server->on('error', function (\Exception $ex) use (&$exception) { @@ -2787,11 +2610,9 @@ function ($data) use (&$buffer) { public function testResolveWrongTypeInPromiseWillResultInError() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return \React\Promise\resolve("invalid"); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return \React\Promise\resolve("invalid"); + }); $buffer = ''; $this->connection @@ -2819,13 +2640,11 @@ function ($data) use (&$buffer) { public function testRejectedPromiseWillResultInErrorMessage() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return new Promise(function ($resolve, $reject) { - $reject(new \Exception()); - }); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return new Promise(function ($resolve, $reject) { + $reject(new \Exception()); + }); + }); $server->on('error', $this->expectCallableOnce()); $buffer = ''; @@ -2854,13 +2673,11 @@ function ($data) use (&$buffer) { public function testExcpetionInCallbackWillResultInErrorMessage() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return new Promise(function ($resolve, $reject) { - throw new \Exception('Bad call'); - }); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return new Promise(function ($resolve, $reject) { + throw new \Exception('Bad call'); + }); + }); $server->on('error', $this->expectCallableOnce()); $buffer = ''; @@ -2889,11 +2706,9 @@ function ($data) use (&$buffer) { public function testHeaderWillAlwaysBeContentLengthForStringBody() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return new Response(200, array('Transfer-Encoding' => 'chunked'), 'hello'); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return new Response(200, array('Transfer-Encoding' => 'chunked'), 'hello'); + }); $buffer = ''; $this->connection @@ -2925,11 +2740,9 @@ function ($data) use (&$buffer) { public function testReturnRequestWillBeHandled() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return new Response(); + }); $buffer = ''; $this->connection @@ -2957,11 +2770,9 @@ function ($data) use (&$buffer) { public function testExceptionThrowInCallBackFunctionWillResultInErrorMessage() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - throw new \Exception('hello'); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + throw new \Exception('hello'); + }); $exception = null; $server->on('error', function (\Exception $ex) use (&$exception) { @@ -2999,11 +2810,9 @@ function ($data) use (&$buffer) { */ public function testThrowableThrowInCallBackFunctionWillResultInErrorMessage() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - throw new \Error('hello'); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + throw new \Error('hello'); + }); $exception = null; $server->on('error', function (\Exception $ex) use (&$exception) { @@ -3044,13 +2853,11 @@ function ($data) use (&$buffer) { public function testRejectOfNonExceptionWillResultInErrorMessage() { - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) { - return new Promise(function ($resolve, $reject) { - $reject('Invalid type'); - }); - }) - )); + $server = new Server(function (ServerRequestInterface $request) { + return new Promise(function ($resolve, $reject) { + $reject('Invalid type'); + }); + }); $exception = null; $server->on('error', function (\Exception $ex) use (&$exception) { @@ -3085,12 +2892,10 @@ function ($data) use (&$buffer) { public function testServerRequestParams() { $requestValidation = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestValidation) { - $requestValidation = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestValidation) { + $requestValidation = $request; + return new Response(); + }); $this->connection ->expects($this->any()) @@ -3122,12 +2927,10 @@ public function testServerRequestParams() public function testQueryParametersWillBeAddedToRequest() { $requestValidation = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestValidation) { - $requestValidation = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestValidation) { + $requestValidation = $request; + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -3145,12 +2948,10 @@ public function testQueryParametersWillBeAddedToRequest() public function testCookieWillBeAddedToServerRequest() { $requestValidation = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestValidation) { - $requestValidation = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestValidation) { + $requestValidation = $request; + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -3169,12 +2970,10 @@ public function testCookieWillBeAddedToServerRequest() public function testMultipleCookiesWontBeAddedToServerRequest() { $requestValidation = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestValidation) { - $requestValidation = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestValidation) { + $requestValidation = $request; + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); @@ -3193,12 +2992,10 @@ public function testMultipleCookiesWontBeAddedToServerRequest() public function testCookieWithSeparatorWillBeAddedToServerRequest() { $requestValidation = null; - $server = new Server(array( - new Callback(function (ServerRequestInterface $request) use (&$requestValidation) { - $requestValidation = $request; - return new Response(); - }) - )); + $server = new Server(function (ServerRequestInterface $request) use (&$requestValidation) { + $requestValidation = $request; + return new Response(); + }); $server->listen($this->socket); $this->socket->emit('connection', array($this->connection)); From 21aff47279c2e8f3e1dda874def82fbe2568405a Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 19 Aug 2017 17:49:13 +0200 Subject: [PATCH 13/22] PHP 5.3 compatibility fixes --- tests/Middleware/BufferTest.php | 2 +- tests/MiddlewareStackTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Middleware/BufferTest.php b/tests/Middleware/BufferTest.php index c9bff90b..d209efb4 100644 --- a/tests/Middleware/BufferTest.php +++ b/tests/Middleware/BufferTest.php @@ -27,7 +27,7 @@ public function testBuffer() $exposeRequest = new ExposeRequest(); $response = new Response(); - $stack = new MiddlewareStack($response, [$exposeRequest]); + $stack = new MiddlewareStack($response, array($exposeRequest)); $buffer = new Buffer(); $buffer->process($serverRequest, $stack); diff --git a/tests/MiddlewareStackTest.php b/tests/MiddlewareStackTest.php index 6848a7e3..b727fe3e 100644 --- a/tests/MiddlewareStackTest.php +++ b/tests/MiddlewareStackTest.php @@ -15,7 +15,7 @@ public function testDefaultResponse() { $request = new ServerRequest('GET', 'https://example.com/'); $defaultResponse = new Response(404); - $middlewares = []; + $middlewares = array(); $middlewareStack = new MiddlewareStack($defaultResponse, $middlewares); $result = Block\await($middlewareStack->process($request), Factory::create()); From 567f92473cb5b68e6f6d52bc06ffa627e6e22b4b Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 19 Aug 2017 17:58:44 +0200 Subject: [PATCH 14/22] Use fully qualified class names in Server::__construct InvalidArgumentException --- src/Server.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Server.php b/src/Server.php index 16532082..3666ebc9 100644 --- a/src/Server.php +++ b/src/Server.php @@ -118,7 +118,7 @@ public function __construct($middlewares) return; } - throw new \InvalidArgumentException('Only callables, MiddlewareInterface[], or MiddlewareStackInterface implementations are supported'); + throw new \InvalidArgumentException('Only a callable, React\Http\MiddlewareInterface[], or a single React\Http\MiddlewareStackInterface implementation are supported'); } /** From 1dfb7840e749c3f9605bb04cfa261cdb8f9cc9d5 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 21 Aug 2017 15:54:13 +0200 Subject: [PATCH 15/22] Catch exceptions in MiddlewareStack --- src/MiddlewareStack.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/MiddlewareStack.php b/src/MiddlewareStack.php index 3b91be2a..417a8daa 100644 --- a/src/MiddlewareStack.php +++ b/src/MiddlewareStack.php @@ -42,14 +42,16 @@ public function process(ServerRequestInterface $request) $middlewares = $this->middlewares; $middleware = array_shift($middlewares); - return Promise\resolve( - $middleware->process( + return new Promise\Promise(function ($resolve, $reject) use ($request, $middleware, $middlewares) { + $response = $middleware->process( $request, new self( $this->defaultResponse, $middlewares ) - ) - ); + ); + + $resolve($response); + }); } } From 10525977eb1d8179f81098f0c6b2a5c8d806f6e8 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 22 Aug 2017 23:13:16 +0200 Subject: [PATCH 16/22] Moved exception catching to the Callback middleware and MiddlewareStack; also forward cancels correctly --- src/Middleware/Callback.php | 20 ++++++++++++++------ src/MiddlewareStack.php | 12 ++++++++---- src/Server.php | 12 ++++-------- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/Middleware/Callback.php b/src/Middleware/Callback.php index 51a408b4..d04f2e40 100644 --- a/src/Middleware/Callback.php +++ b/src/Middleware/Callback.php @@ -21,13 +21,21 @@ public function process(ServerRequestInterface $request, MiddlewareStackInterfac { $callback = $this->callback; - return Promise\resolve($callback($request))->then(function ($response) use ($stack) { - if ($response instanceof ResponseInterface) { - return $response; - } + $cancel = null; + return new Promise\Promise(function ($resolve, $reject) use ($callback, $request, &$cancel, $stack) { + $cancel = Promise\resolve($callback($request)); + $cancel->then(function ($response) use ($stack) { + if ($response instanceof ResponseInterface) { + return $response; + } - // Assuming since $response isn't a response it is a request - return $stack->process($response); + // Assuming since $response isn't a response it is a request + return $stack->process($response); + })->done($resolve, $reject); + }, function () use (&$cancel) { + if ($cancel instanceof Promise\CancellablePromiseInterface) { + $cancel->cancel(); + } }); } } diff --git a/src/MiddlewareStack.php b/src/MiddlewareStack.php index 417a8daa..b45bfd84 100644 --- a/src/MiddlewareStack.php +++ b/src/MiddlewareStack.php @@ -42,16 +42,20 @@ public function process(ServerRequestInterface $request) $middlewares = $this->middlewares; $middleware = array_shift($middlewares); - return new Promise\Promise(function ($resolve, $reject) use ($request, $middleware, $middlewares) { - $response = $middleware->process( + $cancel = null; + return new Promise\Promise(function ($resolve, $reject) use ($middleware, $request, $middlewares, &$cancel) { + $cancel = $middleware->process( $request, new self( $this->defaultResponse, $middlewares ) ); - - $resolve($response); + $cancel->done($resolve, $reject); + }, function () use (&$cancel) { + if ($cancel instanceof Promise\CancellablePromiseInterface) { + $cancel->cancel(); + } }); } } diff --git a/src/Server.php b/src/Server.php index 3666ebc9..b0f2193b 100644 --- a/src/Server.php +++ b/src/Server.php @@ -252,16 +252,12 @@ public function handleRequest(ConnectionInterface $conn, ServerRequestInterface } $middlewareStack = $this->middlewareStack; - $cancel = null; - $promise = new Promise\Promise(function ($resolve, $reject) use ($middlewareStack, $request, &$cancel) { - $cancel = $middlewareStack->process($request); - $resolve($cancel); - }); + $promise = $middlewareStack->process($request); // cancel pending promise once connection closes - if ($cancel instanceof CancellablePromiseInterface) { - $conn->on('close', function () use ($cancel) { - $cancel->cancel(); + if ($promise instanceof CancellablePromiseInterface) { + $conn->on('close', function () use ($promise) { + $promise->cancel(); }); } From 8cfcdf5e482add4e4939f73d2d72766cac3504a8 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 22 Aug 2017 23:17:40 +0200 Subject: [PATCH 17/22] self => MiddlewareStack --- src/MiddlewareStack.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MiddlewareStack.php b/src/MiddlewareStack.php index b45bfd84..2258df3e 100644 --- a/src/MiddlewareStack.php +++ b/src/MiddlewareStack.php @@ -46,7 +46,7 @@ public function process(ServerRequestInterface $request) return new Promise\Promise(function ($resolve, $reject) use ($middleware, $request, $middlewares, &$cancel) { $cancel = $middleware->process( $request, - new self( + new MiddlewareStack( $this->defaultResponse, $middlewares ) From 3fef536b55a079960e4b0873dc2c35b57e09a60e Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 22 Aug 2017 23:22:34 +0200 Subject: [PATCH 18/22] PHP 5.3 compatibility --- src/MiddlewareStack.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/MiddlewareStack.php b/src/MiddlewareStack.php index 2258df3e..cf29f9f6 100644 --- a/src/MiddlewareStack.php +++ b/src/MiddlewareStack.php @@ -42,12 +42,13 @@ public function process(ServerRequestInterface $request) $middlewares = $this->middlewares; $middleware = array_shift($middlewares); + $that = $this; $cancel = null; - return new Promise\Promise(function ($resolve, $reject) use ($middleware, $request, $middlewares, &$cancel) { + return new Promise\Promise(function ($resolve, $reject) use ($middleware, $request, $middlewares, &$cancel, $that) { $cancel = $middleware->process( $request, new MiddlewareStack( - $this->defaultResponse, + $that->defaultResponse, $middlewares ) ); From b4211aa2b0f303965c1b0224da866b096c90aed4 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 4 Sep 2017 21:53:35 +0200 Subject: [PATCH 19/22] Removed middleware related interfaces and assume all middlewares (and runners/stacks) are callables. As a result the Callback middleware has been removed as it has become obsolete --- src/Middleware/Buffer.php | 10 +- src/Middleware/Callback.php | 41 ------- src/Middleware/LimitHandlers.php | 6 +- src/MiddlewareInterface.php | 21 ---- src/MiddlewareStack.php | 10 +- src/MiddlewareStackInterface.php | 15 --- src/Server.php | 46 ++++---- tests/FunctionalServerTest.php | 178 ++++++++++++----------------- tests/Middleware/BufferTest.php | 9 +- tests/Middleware/CallbackTest.php | 65 ----------- tests/Middleware/ExposeRequest.php | 8 +- tests/Middleware/ProcessStack.php | 8 +- tests/MiddlewareStackTest.php | 4 +- 13 files changed, 118 insertions(+), 303 deletions(-) delete mode 100644 src/Middleware/Callback.php delete mode 100644 src/MiddlewareInterface.php delete mode 100644 src/MiddlewareStackInterface.php delete mode 100644 tests/Middleware/CallbackTest.php diff --git a/src/Middleware/Buffer.php b/src/Middleware/Buffer.php index ed657f31..81efd98c 100644 --- a/src/Middleware/Buffer.php +++ b/src/Middleware/Buffer.php @@ -3,14 +3,12 @@ namespace React\Http\Middleware; use Psr\Http\Message\ServerRequestInterface; -use React\Http\MiddlewareInterface; -use React\Http\MiddlewareStackInterface; use React\Http\Response; use React\Promise\Stream; use React\Stream\ReadableStreamInterface; use RingCentral\Psr7\BufferStream; -final class Buffer implements MiddlewareInterface +final class Buffer { private $sizeLimit; @@ -23,7 +21,7 @@ public function __construct($sizeLimit = null) $this->sizeLimit = $sizeLimit; } - public function process(ServerRequestInterface $request, MiddlewareStackInterface $stack) + public function __invoke(ServerRequestInterface $request, callable $stack) { $size = $request->getBody()->getSize(); @@ -37,7 +35,7 @@ public function process(ServerRequestInterface $request, MiddlewareStackInterfac $body = $request->getBody(); if (!$body instanceof ReadableStreamInterface) { - return $stack->process($request); + return $stack($request); } return Stream\buffer($body)->then(function ($buffer) use ($request, $stack) { @@ -45,7 +43,7 @@ public function process(ServerRequestInterface $request, MiddlewareStackInterfac $stream->write($buffer); $request = $request->withBody($stream); - return $stack->process($request); + return $stack($request); }); } diff --git a/src/Middleware/Callback.php b/src/Middleware/Callback.php deleted file mode 100644 index d04f2e40..00000000 --- a/src/Middleware/Callback.php +++ /dev/null @@ -1,41 +0,0 @@ -callback = $callback; - } - - public function process(ServerRequestInterface $request, MiddlewareStackInterface $stack) - { - $callback = $this->callback; - - $cancel = null; - return new Promise\Promise(function ($resolve, $reject) use ($callback, $request, &$cancel, $stack) { - $cancel = Promise\resolve($callback($request)); - $cancel->then(function ($response) use ($stack) { - if ($response instanceof ResponseInterface) { - return $response; - } - - // Assuming since $response isn't a response it is a request - return $stack->process($response); - })->done($resolve, $reject); - }, function () use (&$cancel) { - if ($cancel instanceof Promise\CancellablePromiseInterface) { - $cancel->cancel(); - } - }); - } -} diff --git a/src/Middleware/LimitHandlers.php b/src/Middleware/LimitHandlers.php index 784eb6f2..a0640e7a 100644 --- a/src/Middleware/LimitHandlers.php +++ b/src/Middleware/LimitHandlers.php @@ -7,7 +7,7 @@ use React\Http\MiddlewareStackInterface; use React\Promise\Deferred; -final class LimitHandlers implements MiddlewareInterface +final class LimitHandlers { private $limit = 10; private $pending = 0; @@ -19,7 +19,7 @@ public function __construct($limit = 10) $this->queued = new \SplQueue(); } - public function process(ServerRequestInterface $request, MiddlewareStackInterface $stack) + public function __invoke(ServerRequestInterface $request, callable $stack) { $deferred = new Deferred(); $this->queued->enqueue($deferred); @@ -28,7 +28,7 @@ public function process(ServerRequestInterface $request, MiddlewareStackInterfac return $deferred->promise()->then(function () use ($request, $stack) { $this->pending++; - return $stack->process($request); + return $stack($request); })->then(function ($response) { $this->pending--; $this->processQueue(); diff --git a/src/MiddlewareInterface.php b/src/MiddlewareInterface.php deleted file mode 100644 index ef20d948..00000000 --- a/src/MiddlewareInterface.php +++ /dev/null @@ -1,21 +0,0 @@ - - */ - public function process(ServerRequestInterface $request, MiddlewareStackInterface $stack); -} diff --git a/src/MiddlewareStack.php b/src/MiddlewareStack.php index cf29f9f6..a1537d0d 100644 --- a/src/MiddlewareStack.php +++ b/src/MiddlewareStack.php @@ -7,7 +7,7 @@ use React\Promise; use React\Promise\PromiseInterface; -final class MiddlewareStack implements MiddlewareStackInterface +class MiddlewareStack { /** * @var ResponseInterface @@ -15,13 +15,13 @@ final class MiddlewareStack implements MiddlewareStackInterface private $defaultResponse; /** - * @var MiddlewareInterface[] + * @var callable[] */ private $middlewares = array(); /** * @param ResponseInterface $response - * @param MiddlewareInterface[] $middlewares + * @param callable[] $middlewares */ public function __construct(ResponseInterface $response, array $middlewares) { @@ -33,7 +33,7 @@ public function __construct(ResponseInterface $response, array $middlewares) * @param ServerRequestInterface $request * @return PromiseInterface */ - public function process(ServerRequestInterface $request) + public function __invoke(ServerRequestInterface $request) { if (count($this->middlewares) === 0) { return Promise\resolve($this->defaultResponse); @@ -45,7 +45,7 @@ public function process(ServerRequestInterface $request) $that = $this; $cancel = null; return new Promise\Promise(function ($resolve, $reject) use ($middleware, $request, $middlewares, &$cancel, $that) { - $cancel = $middleware->process( + $cancel = $middleware( $request, new MiddlewareStack( $that->defaultResponse, diff --git a/src/MiddlewareStackInterface.php b/src/MiddlewareStackInterface.php deleted file mode 100644 index 62947aa2..00000000 --- a/src/MiddlewareStackInterface.php +++ /dev/null @@ -1,15 +0,0 @@ - - */ - public function process(ServerRequestInterface $request); -} diff --git a/src/Server.php b/src/Server.php index b0f2193b..a3de5c62 100644 --- a/src/Server.php +++ b/src/Server.php @@ -3,7 +3,6 @@ namespace React\Http; use Evenement\EventEmitter; -use React\Http\Middleware\Callback; use React\Socket\ServerInterface; use React\Socket\ConnectionInterface; use Psr\Http\Message\RequestInterface; @@ -79,9 +78,9 @@ class Server extends EventEmitter { /** - * @var MiddlewareStackInterface + * @var callable */ - private $middlewareStack; + private $callable; /** * Creates an HTTP server that invokes the given middleware stack for each @@ -94,31 +93,17 @@ class Server extends EventEmitter * connections in order to then parse incoming data as HTTP. * See also [listen()](#listen) for more details. * - * @param callable|MiddlewareInterface[]|MiddlewareStackInterface $middlewares + * @param callable $callable * @see self::listen() */ - public function __construct($middlewares) + public function __construct($callable) { - if (is_callable($middlewares)) { - $middlewares = array( - new Callback($middlewares), - ); - } - - if (is_array($middlewares)) { - $this->middlewareStack = new MiddlewareStack( - new Response(404), - $middlewares - ); + if (is_callable($callable)) { + $this->callable = $callable; return; } - if ($middlewares instanceof MiddlewareStackInterface) { - $this->middlewareStack = $middlewares; - return; - } - - throw new \InvalidArgumentException('Only a callable, React\Http\MiddlewareInterface[], or a single React\Http\MiddlewareStackInterface implementation are supported'); + throw new \InvalidArgumentException('Only a callables are supported'); } /** @@ -251,8 +236,21 @@ public function handleRequest(ConnectionInterface $conn, ServerRequestInterface $conn->write("HTTP/1.1 100 Continue\r\n\r\n"); } - $middlewareStack = $this->middlewareStack; - $promise = $middlewareStack->process($request); + $cancel = null; + $promise = new Promise\Promise(function ($resolve, $reject) use ($request, &$cancel) { + $callable = $this->callable; + $cancel = $callable($request); + if ($cancel instanceof Promise\CancellablePromiseInterface) { + $cancel->done($resolve, $reject); + return; + } + + $resolve($cancel); + }, function () use (&$cancel) { + if ($cancel instanceof Promise\CancellablePromiseInterface) { + $cancel->cancel(); + } + }); // cancel pending promise once connection closes if ($promise instanceof CancellablePromiseInterface) { diff --git a/tests/FunctionalServerTest.php b/tests/FunctionalServerTest.php index c049f69c..847bb090 100644 --- a/tests/FunctionalServerTest.php +++ b/tests/FunctionalServerTest.php @@ -26,11 +26,9 @@ public function testPlainHttpOnRandomPort() $loop = Factory::create(); $connector = new Connector($loop); - $server = new Server(array( - new Callback(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }) - )); + $server = new Server(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }); $socket = new Socket(0, $loop); $server->listen($socket); @@ -54,11 +52,9 @@ public function testPlainHttpOnRandomPortWithoutHostHeaderUsesSocketUri() $loop = Factory::create(); $connector = new Connector($loop); - $server = new Server(array( - new Callback(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }) - )); + $server = new Server(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }); $socket = new Socket(0, $loop); $server->listen($socket); @@ -82,11 +78,9 @@ public function testPlainHttpOnRandomPortWithOtherHostHeaderTakesPrecedence() $loop = Factory::create(); $connector = new Connector($loop); - $server = new Server(array( - new Callback(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }) - )); + $server = new Server(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }); $socket = new Socket(0, $loop); $server->listen($socket); @@ -116,11 +110,9 @@ public function testSecureHttpsOnRandomPort() 'tls' => array('verify_peer' => false) )); - $server = new Server(array( - new Callback(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }) - )); + $server = new Server(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }); $socket = new Socket(0, $loop); $socket = new SecureServer($socket, $loop, array( @@ -153,11 +145,9 @@ public function testSecureHttpsOnRandomPortWithoutHostHeaderUsesSocketUri() 'tls' => array('verify_peer' => false) )); - $server = new Server(array( - new Callback(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }) - )); + $server = new Server(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }); $socket = new Socket(0, $loop); $socket = new SecureServer($socket, $loop, array( @@ -189,11 +179,9 @@ public function testPlainHttpOnStandardPortReturnsUriWithNoPort() } $connector = new Connector($loop); - $server = new Server(array( - new Callback(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }) - )); + $server = new Server(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }); $server->listen($socket); @@ -221,11 +209,9 @@ public function testPlainHttpOnStandardPortWithoutHostHeaderReturnsUriWithNoPort } $connector = new Connector($loop); - $server = new Server(array( - new Callback(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }) - )); + $server = new Server(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }); $server->listen($socket); @@ -262,11 +248,9 @@ public function testSecureHttpsOnStandardPortReturnsUriWithNoPort() 'tls' => array('verify_peer' => false) )); - $server = new Server(array( - new Callback(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }) - )); + $server = new Server(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }); $server->listen($socket); @@ -303,11 +287,9 @@ public function testSecureHttpsOnStandardPortWithoutHostHeaderUsesSocketUri() 'tls' => array('verify_peer' => false) )); - $server = new Server(array( - new Callback(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }) - )); + $server = new Server(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }); $server->listen($socket); @@ -335,11 +317,9 @@ public function testPlainHttpOnHttpsStandardPortReturnsUriWithPort() } $connector = new Connector($loop); - $server = new Server(array( - new Callback(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri()); - }) - )); + $server = new Server(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri()); + }); $server->listen($socket); @@ -376,11 +356,9 @@ public function testSecureHttpsOnHttpStandardPortReturnsUriWithPort() 'tls' => array('verify_peer' => false) )); - $server = new Server(array( - new Callback(function (RequestInterface $request) { - return new Response(200, array(), (string)$request->getUri() . 'x' . $request->getHeaderLine('Host')); - }) - )); + $server = new Server(function (RequestInterface $request) { + return new Response(200, array(), (string)$request->getUri() . 'x' . $request->getHeaderLine('Host')); + }); $server->listen($socket); @@ -406,11 +384,9 @@ public function testClosedStreamFromRequestHandlerWillSendEmptyBody() $stream = new ThroughStream(); $stream->close(); - $server = new Server(array( - new Callback(function (RequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }) - )); + $server = new Server(function (RequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }); $socket = new Socket(0, $loop); $server->listen($socket); @@ -437,11 +413,9 @@ public function testStreamFromRequestHandlerWillBeClosedIfConnectionClosesWhileS $stream = new ThroughStream(); $stream->on('close', $this->expectCallableOnce()); - $server = new Server(array( - new Callback(function (RequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }), - )); + $server = new Server(function (RequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }); $socket = new Socket(0, $loop); $server->listen($socket); @@ -472,11 +446,9 @@ public function testStreamFromRequestHandlerWillBeClosedIfConnectionClosesButWil $stream = new ThroughStream(); $stream->on('close', $this->expectCallableOnce()); - $server = new Server(array( - new Callback(function (RequestInterface $request) use ($stream) { - return new Response(200, array(), $stream); - }) - )); + $server = new Server(function (RequestInterface $request) use ($stream) { + return new Response(200, array(), $stream); + }); $socket = new Socket(0, $loop); $server->listen($socket); @@ -509,17 +481,15 @@ public function testUpgradeWithThroughStreamReturnsDataAsGiven() $loop = Factory::create(); $connector = new Connector($loop); - $server = new Server(array( - new Callback(function (RequestInterface $request) use ($loop) { - $stream = new ThroughStream(); + $server = new Server(function (RequestInterface $request) use ($loop) { + $stream = new ThroughStream(); - $loop->addTimer(0.1, function () use ($stream) { - $stream->end(); - }); + $loop->addTimer(0.1, function () use ($stream) { + $stream->end(); + }); - return new Response(101, array('Upgrade' => 'echo'), $stream); - }) - )); + return new Response(101, array('Upgrade' => 'echo'), $stream); + }); $socket = new Socket(0, $loop); $server->listen($socket); @@ -548,17 +518,15 @@ public function testConnectWithThroughStreamReturnsDataAsGiven() $loop = Factory::create(); $connector = new Connector($loop); - $server = new Server(array( - new Callback(function (RequestInterface $request) use ($loop) { - $stream = new ThroughStream(); + $server = new Server(function (RequestInterface $request) use ($loop) { + $stream = new ThroughStream(); - $loop->addTimer(0.1, function () use ($stream) { - $stream->end(); - }); + $loop->addTimer(0.1, function () use ($stream) { + $stream->end(); + }); - return new Response(200, array(), $stream); - }) - )); + return new Response(200, array(), $stream); + }); $socket = new Socket(0, $loop); $server->listen($socket); @@ -587,21 +555,19 @@ public function testConnectWithThroughStreamReturnedFromPromiseReturnsDataAsGive $loop = Factory::create(); $connector = new Connector($loop); - $server = new Server(array( - new Callback(function (RequestInterface $request) use ($loop) { - $stream = new ThroughStream(); + $server = new Server(function (RequestInterface $request) use ($loop) { + $stream = new ThroughStream(); - $loop->addTimer(0.1, function () use ($stream) { - $stream->end(); - }); + $loop->addTimer(0.1, function () use ($stream) { + $stream->end(); + }); - return new Promise(function ($resolve) use ($loop, $stream) { - $loop->addTimer(0.001, function () use ($resolve, $stream) { - $resolve(new Response(200, array(), $stream)); - }); + return new Promise(function ($resolve) use ($loop, $stream) { + $loop->addTimer(0.001, function () use ($resolve, $stream) { + $resolve(new Response(200, array(), $stream)); }); - }) - )); + }); + }); $socket = new Socket(0, $loop); $server->listen($socket); @@ -630,14 +596,12 @@ public function testConnectWithClosedThroughStreamReturnsNoData() $loop = Factory::create(); $connector = new Connector($loop); - $server = new Server(array( - new Callback(function (RequestInterface $request) { - $stream = new ThroughStream(); - $stream->close(); + $server = new Server(function (RequestInterface $request) { + $stream = new ThroughStream(); + $stream->close(); - return new Response(200, array(), $stream); - }) - )); + return new Response(200, array(), $stream); + }); $socket = new Socket(0, $loop); $server->listen($socket); diff --git a/tests/Middleware/BufferTest.php b/tests/Middleware/BufferTest.php index d209efb4..c7d136de 100644 --- a/tests/Middleware/BufferTest.php +++ b/tests/Middleware/BufferTest.php @@ -30,7 +30,7 @@ public function testBuffer() $stack = new MiddlewareStack($response, array($exposeRequest)); $buffer = new Buffer(); - $buffer->process($serverRequest, $stack); + $buffer($serverRequest, $stack); $exposedRequest = $exposeRequest->getRequest(); $this->assertSame($body, $exposedRequest->getBody()->getContents()); @@ -49,15 +49,16 @@ public function testToLargeBody() ); $stack = $this - ->getMockBuilder('React\Http\MiddlewareStackInterface') + ->getMockBuilder('React\Http\MiddlewareStack') + ->disableOriginalConstructor() ->getMock(); $stack ->expects($this->never()) - ->method('process') + ->method('__invoke') ->with($serverRequest); $buffer = new Buffer(); - $response = $buffer->process($serverRequest, $stack); + $response = $buffer($serverRequest, $stack); $this->assertInstanceOf('React\Http\Response', $response); $this->assertSame(413, $response->getStatusCode()); diff --git a/tests/Middleware/CallbackTest.php b/tests/Middleware/CallbackTest.php deleted file mode 100644 index bc7645c4..00000000 --- a/tests/Middleware/CallbackTest.php +++ /dev/null @@ -1,65 +0,0 @@ -getMockBuilder('Psr\Http\Message\ServerRequestInterface') - ->getMock(); - $called = false; - $callback = new Callback(function () use (&$called, $request) { - $called = true; - return $request; - }); - $stack = $this - ->getMockBuilder('React\Http\MiddlewareStackInterface') - ->getMock(); - - $stack - ->expects($this->once()) - ->method('process') - ->with($request) - ->willReturn($request); - - $result = Block\await($callback->process($request, $stack), Factory::create()); - - $this->assertSame($request, $result); - $this->assertTrue($called); - } - - public function testResponse() - { - $request = $this - ->getMockBuilder('Psr\Http\Message\ServerRequestInterface') - ->getMock(); - $response = $this - ->getMockBuilder('Psr\Http\Message\ResponseInterface') - ->getMock(); - $called = false; - $callback = new Callback(function () use (&$called, $response) { - $called = true; - return $response; - }); - $stack = $this - ->getMockBuilder('React\Http\MiddlewareStackInterface') - ->getMock(); - - $stack - ->expects($this->never()) - ->method('process') - ->with($request); - - $result = Block\await($callback->process($request, $stack), Factory::create()); - - $this->assertSame($response, $result); - $this->assertTrue($called); - } -} diff --git a/tests/Middleware/ExposeRequest.php b/tests/Middleware/ExposeRequest.php index a1e20c17..3e48fe60 100644 --- a/tests/Middleware/ExposeRequest.php +++ b/tests/Middleware/ExposeRequest.php @@ -3,21 +3,19 @@ namespace React\Tests\Http\Middleware; use Psr\Http\Message\ServerRequestInterface; -use React\Http\MiddlewareInterface; -use React\Http\MiddlewareStackInterface; use React\Promise; -final class ExposeRequest implements MiddlewareInterface +final class ExposeRequest { /** * @var ServerRequestInterface */ private $request; - public function process(ServerRequestInterface $request, MiddlewareStackInterface $stack) + public function __invoke(ServerRequestInterface $request, callable $stack) { $this->request = $request; - return Promise\resolve($stack->process($request)); + return Promise\resolve($stack($request)); } /** diff --git a/tests/Middleware/ProcessStack.php b/tests/Middleware/ProcessStack.php index 7dd0c60f..a6e2b869 100644 --- a/tests/Middleware/ProcessStack.php +++ b/tests/Middleware/ProcessStack.php @@ -3,21 +3,19 @@ namespace React\Tests\Http\Middleware; use Psr\Http\Message\ServerRequestInterface; -use React\Http\MiddlewareInterface; -use React\Http\MiddlewareStackInterface; use React\Promise; -final class ProcessStack implements MiddlewareInterface +final class ProcessStack { /** * @var int */ private $callCount = 0; - public function process(ServerRequestInterface $request, MiddlewareStackInterface $stack) + public function __invoke(ServerRequestInterface $request, callable $stack) { $this->callCount++; - return Promise\resolve($stack->process($request)); + return Promise\resolve($stack($request)); } /** diff --git a/tests/MiddlewareStackTest.php b/tests/MiddlewareStackTest.php index b727fe3e..48a9af71 100644 --- a/tests/MiddlewareStackTest.php +++ b/tests/MiddlewareStackTest.php @@ -18,7 +18,7 @@ public function testDefaultResponse() $middlewares = array(); $middlewareStack = new MiddlewareStack($defaultResponse, $middlewares); - $result = Block\await($middlewareStack->process($request), Factory::create()); + $result = Block\await($middlewareStack($request), Factory::create()); $this->assertSame($defaultResponse, $result); } @@ -71,7 +71,7 @@ public function testProcessStack(array $middlewares, $expectedCallCount) $defaultResponse = new Response(404); $middlewareStack = new MiddlewareStack($defaultResponse, $middlewares); - $result = Block\await($middlewareStack->process($request), Factory::create()); + $result = Block\await($middlewareStack($request), Factory::create()); $this->assertSame($defaultResponse, $result); foreach ($middlewares as $middleware) { $this->assertSame($expectedCallCount, $middleware->getCallCount()); From 0f6df461a7435fc3eb61c60f1113259136be5b69 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 4 Sep 2017 22:26:59 +0200 Subject: [PATCH 20/22] Updated examples and renamed the MiddlewareStack to MiddlewareRunner --- examples/12-middleware.php | 37 ++++++++++--------- examples/13-request-sec.php | 17 ++++----- ...ddlewareStack.php => MiddlewareRunner.php} | 6 +-- src/Server.php | 19 +++------- tests/FunctionalServerTest.php | 4 -- tests/Middleware/BufferTest.php | 6 +-- ...StackTest.php => MiddlewareRunnerTest.php} | 8 ++-- 7 files changed, 42 insertions(+), 55 deletions(-) rename src/{MiddlewareStack.php => MiddlewareRunner.php} (93%) rename tests/{MiddlewareStackTest.php => MiddlewareRunnerTest.php} (89%) diff --git a/examples/12-middleware.php b/examples/12-middleware.php index 68cb4946..c1146509 100644 --- a/examples/12-middleware.php +++ b/examples/12-middleware.php @@ -3,8 +3,8 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; use React\Http\Middleware\Buffer; -use React\Http\Middleware\Callback; use React\Http\Middleware\LimitHandlers; +use React\Http\MiddlewareRunner; use React\Http\Response; use React\Http\Server; use React\Promise\Deferred; @@ -13,22 +13,25 @@ $loop = Factory::create(); -$server = new Server(array( - new LimitHandlers(3), // Only handle three requests concurrently - new Buffer(), // Buffer the whole request body before proceeding - new Callback(function (ServerRequestInterface $request) use ($loop) { - $deferred = new Deferred(); - $loop->futureTick(function () use ($deferred) { - $deferred->resolve(new Response( - 200, - array( - 'Content-Type' => 'text/plain' - ), - "Hello world\n" - )); - }); - return $deferred->promise(); - }) +$server = new Server(new MiddlewareRunner( + new Response(404), + array( + new LimitHandlers(3), // Only handle three requests concurrently + new Buffer(), // Buffer the whole request body before proceeding + function (ServerRequestInterface $request) use ($loop) { + $deferred = new Deferred(); + $loop->futureTick(function () use ($deferred) { + $deferred->resolve(new Response( + 200, + array( + 'Content-Type' => 'text/plain' + ), + "Hello world\n" + )); + }); + return $deferred->promise(); + }, + ) )); $socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); diff --git a/examples/13-request-sec.php b/examples/13-request-sec.php index 9b216f37..c068b9d4 100644 --- a/examples/13-request-sec.php +++ b/examples/13-request-sec.php @@ -2,10 +2,7 @@ use Psr\Http\Message\ServerRequestInterface; use React\EventLoop\Factory; -use React\Http\Middleware\Callback; -use React\Http\MiddlewareInterface; -use React\Http\MiddlewareStack; -use React\Http\MiddlewareStackInterface; +use React\Http\MiddlewareRunner; use React\Http\Response; use React\Http\Server; use React\Promise\Deferred; @@ -17,14 +14,14 @@ 'requests' => 0, 'responses' => 0, ); -final class Incre implements MiddlewareInterface +final class Incre { - public function process(ServerRequestInterface $request, MiddlewareStackInterface $stack) + public function __invoke(ServerRequestInterface $request, callable $stack) { global $counts, $total; $total++; $counts['requests']++; - return $stack->process($request)->then(function ($response) { + return $stack($request)->then(function ($response) { global $counts; $counts['responses']++; return $response; @@ -44,11 +41,11 @@ public function process(ServerRequestInterface $request, MiddlewareStackInterfac 'responses' => 0, ); }); -$server = new Server(new MiddlewareStack( +$server = new Server(new MiddlewareRunner( new Response(500), array( new Incre($counts), - new Callback(function (ServerRequestInterface $request) use ($loop) { + function (ServerRequestInterface $request) use ($loop) { $deferred = new Deferred(); $loop->addTimer(mt_rand(1, 10) / 10, function () use ($deferred) { $deferred->resolve(new Response( @@ -60,7 +57,7 @@ public function process(ServerRequestInterface $request, MiddlewareStackInterfac )); }); return $deferred->promise(); - }) + } )) ); diff --git a/src/MiddlewareStack.php b/src/MiddlewareRunner.php similarity index 93% rename from src/MiddlewareStack.php rename to src/MiddlewareRunner.php index a1537d0d..349e7a18 100644 --- a/src/MiddlewareStack.php +++ b/src/MiddlewareRunner.php @@ -7,7 +7,7 @@ use React\Promise; use React\Promise\PromiseInterface; -class MiddlewareStack +class MiddlewareRunner { /** * @var ResponseInterface @@ -47,12 +47,12 @@ public function __invoke(ServerRequestInterface $request) return new Promise\Promise(function ($resolve, $reject) use ($middleware, $request, $middlewares, &$cancel, $that) { $cancel = $middleware( $request, - new MiddlewareStack( + new MiddlewareRunner( $that->defaultResponse, $middlewares ) ); - $cancel->done($resolve, $reject); + $cancel->then($resolve, $reject); }, function () use (&$cancel) { if ($cancel instanceof Promise\CancellablePromiseInterface) { $cancel->cancel(); diff --git a/src/Server.php b/src/Server.php index a3de5c62..20370d67 100644 --- a/src/Server.php +++ b/src/Server.php @@ -236,26 +236,17 @@ public function handleRequest(ConnectionInterface $conn, ServerRequestInterface $conn->write("HTTP/1.1 100 Continue\r\n\r\n"); } + $callable = $this->callable; $cancel = null; - $promise = new Promise\Promise(function ($resolve, $reject) use ($request, &$cancel) { - $callable = $this->callable; + $promise = new Promise\Promise(function ($resolve, $reject) use ($callable, $request, &$cancel) { $cancel = $callable($request); - if ($cancel instanceof Promise\CancellablePromiseInterface) { - $cancel->done($resolve, $reject); - return; - } - $resolve($cancel); - }, function () use (&$cancel) { - if ($cancel instanceof Promise\CancellablePromiseInterface) { - $cancel->cancel(); - } }); // cancel pending promise once connection closes - if ($promise instanceof CancellablePromiseInterface) { - $conn->on('close', function () use ($promise) { - $promise->cancel(); + if ($cancel instanceof CancellablePromiseInterface) { + $conn->on('close', function () use ($cancel) { + $cancel->cancel(); }); } diff --git a/tests/FunctionalServerTest.php b/tests/FunctionalServerTest.php index 847bb090..b0eadef9 100644 --- a/tests/FunctionalServerTest.php +++ b/tests/FunctionalServerTest.php @@ -2,8 +2,6 @@ namespace React\Tests\Http; -use React\Http\Middleware\Buffer; -use React\Http\Middleware\Callback; use React\Socket\Server as Socket; use React\EventLoop\Factory; use React\Http\Server; @@ -13,9 +11,7 @@ use Clue\React\Block; use React\Http\Response; use React\Socket\SecureServer; -use React\Stream\ReadableStreamInterface; use React\Promise\Promise; -use React\Promise\PromiseInterface; use React\Promise\Stream; use React\Stream\ThroughStream; diff --git a/tests/Middleware/BufferTest.php b/tests/Middleware/BufferTest.php index c7d136de..9a92dd51 100644 --- a/tests/Middleware/BufferTest.php +++ b/tests/Middleware/BufferTest.php @@ -3,7 +3,7 @@ namespace React\Tests\Http\Middleware; use React\Http\Middleware\Buffer; -use React\Http\MiddlewareStack; +use React\Http\MiddlewareRunner; use React\Http\Response; use React\Http\ServerRequest; use React\Tests\Http\TestCase; @@ -27,7 +27,7 @@ public function testBuffer() $exposeRequest = new ExposeRequest(); $response = new Response(); - $stack = new MiddlewareStack($response, array($exposeRequest)); + $stack = new MiddlewareRunner($response, array($exposeRequest)); $buffer = new Buffer(); $buffer($serverRequest, $stack); @@ -49,7 +49,7 @@ public function testToLargeBody() ); $stack = $this - ->getMockBuilder('React\Http\MiddlewareStack') + ->getMockBuilder('React\Http\MiddlewareRunner') ->disableOriginalConstructor() ->getMock(); $stack diff --git a/tests/MiddlewareStackTest.php b/tests/MiddlewareRunnerTest.php similarity index 89% rename from tests/MiddlewareStackTest.php rename to tests/MiddlewareRunnerTest.php index 48a9af71..983dbca5 100644 --- a/tests/MiddlewareStackTest.php +++ b/tests/MiddlewareRunnerTest.php @@ -3,20 +3,20 @@ namespace React\Tests\Http; use React\EventLoop\Factory; -use React\Http\MiddlewareStack; +use React\Http\MiddlewareRunner; use React\Http\ServerRequest; use React\Tests\Http\Middleware\ProcessStack; use RingCentral\Psr7\Response; use Clue\React\Block; -final class MiddlewareStackTest extends TestCase +final class MiddlewareRunnerTest extends TestCase { public function testDefaultResponse() { $request = new ServerRequest('GET', 'https://example.com/'); $defaultResponse = new Response(404); $middlewares = array(); - $middlewareStack = new MiddlewareStack($defaultResponse, $middlewares); + $middlewareStack = new MiddlewareRunner($defaultResponse, $middlewares); $result = Block\await($middlewareStack($request), Factory::create()); $this->assertSame($defaultResponse, $result); @@ -69,7 +69,7 @@ public function testProcessStack(array $middlewares, $expectedCallCount) { $request = new ServerRequest('GET', 'https://example.com/'); $defaultResponse = new Response(404); - $middlewareStack = new MiddlewareStack($defaultResponse, $middlewares); + $middlewareStack = new MiddlewareRunner($defaultResponse, $middlewares); $result = Block\await($middlewareStack($request), Factory::create()); $this->assertSame($defaultResponse, $result); From 7c32bc1317140d9fc4033e098a1cf62008963592 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 5 Sep 2017 16:55:47 +0200 Subject: [PATCH 21/22] Restored Server.php documentation --- src/Server.php | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Server.php b/src/Server.php index 20370d67..e36830f7 100644 --- a/src/Server.php +++ b/src/Server.php @@ -17,20 +17,18 @@ * The `Server` class is responsible for handling incoming connections and then * processing each incoming HTTP request. * - * For each request, it executes the middleware stack passed to the + * For each request, it executes the callback function passed to the * constructor with the respective [request](#request) object and expects * a respective [response](#response) object in return. * * ```php - * $server = new Server([ - * new Callback(function (ServerRequestInterface $request) { - * return new Response( - * 200, - * array('Content-Type' => 'text/plain'), - * "Hello World!\n" - * ); - * }) - * ]); + * $server = new Server(function (ServerRequestInterface $request) { + * return new Response( + * 200, + * array('Content-Type' => 'text/plain'), + * "Hello World!\n" + * ); + * }); * ``` * * In order to process any connections, the server needs to be attached to an From 4e9d514fa9c37b91ec8db68dbb628392261e900f Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 5 Sep 2017 16:56:48 +0200 Subject: [PATCH 22/22] Restored Server.php constructor documentation --- src/Server.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Server.php b/src/Server.php index e36830f7..4e12a57f 100644 --- a/src/Server.php +++ b/src/Server.php @@ -81,10 +81,7 @@ class Server extends EventEmitter private $callable; /** - * Creates an HTTP server that invokes the given middleware stack for each - * incoming HTTP request. The middleware stack is either a concrete class - * implementing `React\Http\MiddlewareStackInterface` or an array of - * `React\Http\MiddlewareInterface`s. + * Creates an HTTP server that invokes the given callback for each incoming HTTP request * * In order to process any connections, the server needs to be attached to an * instance of `React\Socket\ServerInterface` which emits underlying streaming