Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 166
Middleware#213
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Middleware #213
Changes from all commits
5e29f516ecd9dce883ae89e318ed0447be81eac846f7fa17ff27febbad7f8bb4699ea09c3a09aba9e744ece043b21aff47567f9241dfb78410525978cfcdf53fef536b4211aa0f6df467c32bc14e9d514File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
| @@ -136,6 +137,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 | ||
| 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. | ||
| 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); | ||
| }), | ||
| ]; | ||
| ``` | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leftover from b4211aa | ||
| #### Buffer | ||
| Another build in middleware is `Buffer` which will buffer the incoming | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This PR is superseded by smaller PR's split off, eg. #216 (which is already merged). This PR should probably closed by @WyriHaximus to avoid confusion. MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done 👍 | ||
| 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) { | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still using | ||
| return new Response(200); | ||
| }), | ||
| ]; | ||
| ``` | ||
| ### Request | ||
| An seen above, the `Server` class is responsible for handling incoming | ||
| @@ -659,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). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| <?php | ||
| use Psr\Http\Message\ServerRequestInterface; | ||
| use React\EventLoop\Factory; | ||
| use React\Http\Middleware\Buffer; | ||
| use React\Http\Middleware\LimitHandlers; | ||
| use React\Http\MiddlewareRunner; | ||
| use React\Http\Response; | ||
| use React\Http\Server; | ||
| use React\Promise\Deferred; | ||
| require __DIR__ . '/../vendor/autoload.php'; | ||
| $loop = Factory::create(); | ||
| $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); | ||
| $server->listen($socket); | ||
| echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL; | ||
| $loop->run(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| <?php | ||
| use Psr\Http\Message\ServerRequestInterface; | ||
| use React\EventLoop\Factory; | ||
| use React\Http\MiddlewareRunner; | ||
| use React\Http\Response; | ||
| use React\Http\Server; | ||
| use React\Promise\Deferred; | ||
| require __DIR__ . '/../vendor/autoload.php'; | ||
| $total = 0; | ||
| $counts = array( | ||
| 'requests' => 0, | ||
| 'responses' => 0, | ||
| ); | ||
| final class Incre | ||
| { | ||
| public function __invoke(ServerRequestInterface $request, callable $stack) | ||
| { | ||
| global $counts, $total; | ||
| $total++; | ||
| $counts['requests']++; | ||
| return $stack($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(new MiddlewareRunner( | ||
| new Response(500), | ||
| array( | ||
| new Incre($counts), | ||
| 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(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| <?php | ||
| namespace React\Http\Middleware; | ||
| use Psr\Http\Message\ServerRequestInterface; | ||
| use React\Http\Response; | ||
| use React\Promise\Stream; | ||
| use React\Stream\ReadableStreamInterface; | ||
| use RingCentral\Psr7\BufferStream; | ||
| final class Buffer | ||
| { | ||
| private $sizeLimit; | ||
| public function __construct($sizeLimit = null) | ||
| { | ||
| if ($sizeLimit === null) { | ||
| $sizeLimit = $this->iniMaxPostSize(); | ||
| } | ||
| $this->sizeLimit = $sizeLimit; | ||
| } | ||
| public function __invoke(ServerRequestInterface $request, callable $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($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($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; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| <?php | ||
| namespace React\Http\Middleware; | ||
| use Psr\Http\Message\ServerRequestInterface; | ||
| use React\Http\MiddlewareInterface; | ||
| use React\Http\MiddlewareStackInterface; | ||
| use React\Promise\Deferred; | ||
| final class LimitHandlers | ||
| { | ||
| private $limit = 10; | ||
| private $pending = 0; | ||
| private $queued; | ||
| public function __construct($limit = 10) | ||
| { | ||
| $this->limit = $limit; | ||
| $this->queued = new \SplQueue(); | ||
| } | ||
| public function __invoke(ServerRequestInterface $request, callable $stack) | ||
| { | ||
| $deferred = new Deferred(); | ||
| $this->queued->enqueue($deferred); | ||
| $this->processQueue(); | ||
| return $deferred->promise()->then(function () use ($request, $stack) { | ||
| $this->pending++; | ||
| return $stack($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(); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be
built-in?