Skip to content

Commit 0a75b4b

Browse files
committed
Support middleware request handlers as arrays by default
1 parent 262bd0f commit 0a75b4b

6 files changed

Lines changed: 63 additions & 68 deletions

File tree

README.md

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -659,27 +659,49 @@ passed explicitly.
659659

660660
### Middleware
661661

662-
Middleware can be added to the server using [`MiddlewareRunner`](src/MiddlewareRunner.php)
663-
instead of the `callable`. A middleware is expected to adhere the following rules:
662+
As documented above, the [`StreamingServer`](#streamingserver) accepts a single
663+
request handler argument that is responsible for processing an incoming
664+
HTTP request and then creating and returning an outgoing HTTP response.
665+
666+
Many common use cases involve validating, processing, manipulating the incoming
667+
HTTP request before passing it to the final business logic request handler.
668+
As such, this project supports the concept of middleware request handlers.
669+
670+
A middleware request handler is expected to adhere the following rules:
664671

665672
* It is a `callable`.
666673
* It accepts `ServerRequestInterface` as first argument and optional `callable` as second argument.
667674
* It returns a `ResponseInterface` (or any promise which can be consumed by [`Promise\resolve`](http://reactphp.org/promise/#resolve) resolving to a `ResponseInterface`)
668675
* It calls `$next($request)` to continue processing the next middleware function or returns explicitly to abort the chain
669676

670-
The following example adds a middleware that adds the current time to the request as a
671-
header (`Request-Time`) and middleware that always returns a 200 code without a body:
677+
Note that this very simple definition allows you to use either anonymous
678+
functions or any classes that use the magic `__invoke()` method.
679+
This allows you to easily create custom middleware request handlers on the fly
680+
or use a class based approach to ease using existing middleware implementations.
681+
682+
While this project does provide the means to *use* middleware implementations,
683+
it does not aim to *define* how middleware implementations should look like.
684+
We realize that there's a vivid ecosystem of middleware implementations and
685+
ongoing effort to standardize interfaces between these and support this goal.
686+
As such, this project only bundles a few middleware implementations that are
687+
required to match PHP's request behavior (see below) and otherwise actively
688+
encourages [Third-Party Middleware](#third-party-middleware) implementations.
689+
690+
In order to use middleware request handlers, simply pass an array with all
691+
callables as defined above to the [`StreamingServer`](#streamingserver).
692+
The following example adds a middleware request handler that adds the current time to the request as a
693+
header (`Request-Time`) and a final request handler that always returns a 200 code without a body:
672694

673695
```php
674-
$server = new StreamingServer(new MiddlewareRunner([
696+
$server = new StreamingServer(array(
675697
function (ServerRequestInterface $request, callable $next) {
676698
$request = $request->withHeader('Request-Time', time());
677699
return $next($request);
678700
},
679701
function (ServerRequestInterface $request, callable $next) {
680702
return new Response(200);
681703
},
682-
]));
704+
));
683705
```
684706

685707
#### LimitConcurrentRequestsMiddleware
@@ -702,37 +724,37 @@ The following example shows how this middleware can be used to ensure no more
702724
than 10 handlers will be invoked at once:
703725

704726
```php
705-
$server = new StreamingServer(new MiddlewareRunner([
727+
$server = new StreamingServer(array(
706728
new LimitConcurrentRequestsMiddleware(10),
707729
$handler
708-
]));
730+
));
709731
```
710732

711733
Similarly, this middleware is often used in combination with the
712734
[`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) (see below)
713735
to limit the total number of requests that can be buffered at once:
714736

715737
```php
716-
$server = new StreamingServer(new MiddlewareRunner([
738+
$server = new StreamingServer(array(
717739
new LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
718740
new RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
719741
new RequestBodyParserMiddleware(),
720742
$handler
721-
]));
743+
));
722744
```
723745

724746
More sophisticated examples include limiting the total number of requests
725747
that can be buffered at once and then ensure the actual request handler only
726748
processes one request after another without any concurrency:
727749

728750
```php
729-
$server = new StreamingServer(new MiddlewareRunner([
751+
$server = new StreamingServer(array(
730752
new LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
731753
new RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
732754
new RequestBodyParserMiddleware(),
733755
new LimitConcurrentRequestsMiddleware(1), // only execute 1 handler (no concurrency)
734756
$handler
735-
]));
757+
));
736758
```
737759

738760
#### RequestBodyBufferMiddleware
@@ -778,14 +800,14 @@ the total number of concurrent requests.
778800
Usage:
779801

780802
```php
781-
$middlewares = new MiddlewareRunner([
803+
$server = new StreamServer(array(
782804
new LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
783805
new RequestBodyBufferMiddleware(16 * 1024 * 1024), // 16 MiB
784806
function (ServerRequestInterface $request, callable $next) {
785807
// The body from $request->getBody() is now fully available without the need to stream it
786808
return new Response(200);
787809
},
788-
]);
810+
));
789811
```
790812

791813
#### RequestBodyParserMiddleware
@@ -837,12 +859,12 @@ $handler = function (ServerRequestInterface $request) {
837859
);
838860
};
839861

840-
$server = new StreamingServer(new MiddlewareRunner([
862+
$server = new StreamingServer(array((
841863
new LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
842864
new RequestBodyBufferMiddleware(16 * 1024 * 1024), // 16 MiB
843865
new RequestBodyParserMiddleware(),
844866
$handler
845-
]));
867+
));
846868
```
847869

848870
See also [example #12](examples) for more details.

examples/12-upload.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
use Psr\Http\Message\ServerRequestInterface;
1111
use Psr\Http\Message\UploadedFileInterface;
1212
use React\EventLoop\Factory;
13-
use React\Http\MiddlewareRunner;
1413
use React\Http\Middleware\LimitConcurrentRequestsMiddleware;
1514
use React\Http\Middleware\RequestBodyBufferMiddleware;
1615
use React\Http\Middleware\RequestBodyParserMiddleware;
@@ -121,12 +120,12 @@
121120
};
122121

123122
// buffer and parse HTTP request body before running our request handler
124-
$server = new StreamingServer(new MiddlewareRunner(array(
123+
$server = new StreamingServer(array(
125124
new LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers, queue otherwise
126125
new RequestBodyBufferMiddleware(8 * 1024 * 1024), // 8 MiB max, ignore body otherwise
127126
new RequestBodyParserMiddleware(100 * 1024, 1), // 1 file with 100 KiB max, reject upload otherwise
128127
$handler
129-
)));
128+
));
130129

131130
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
132131
$server->listen($socket);

src/Middleware/LimitConcurrentRequestsMiddleware.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,37 +27,37 @@
2727
* than 10 handlers will be invoked at once:
2828
*
2929
* ```php
30-
* $server = new StreamingServer(new MiddlewareRunner([
30+
* $server = new StreamingServer(array(
3131
* new LimitConcurrentRequestsMiddleware(10),
3232
* $handler
33-
* ]));
33+
* ));
3434
* ```
3535
*
3636
* Similarly, this middleware is often used in combination with the
3737
* [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) (see below)
3838
* to limit the total number of requests that can be buffered at once:
3939
*
4040
* ```php
41-
* $server = new StreamingServer(new MiddlewareRunner([
41+
* $server = new StreamingServer(array(
4242
* new LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
4343
* new RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
4444
* new RequestBodyParserMiddleware(),
45-
* $handler
46-
* ]));
45+
* $handler
46+
* ));
4747
* ```
4848
*
4949
* More sophisticated examples include limiting the total number of requests
5050
* that can be buffered at once and then ensure the actual request handler only
5151
* processes one request after another without any concurrency:
5252
*
5353
* ```php
54-
* $server = new StreamingServer(new MiddlewareRunner([
54+
* $server = new StreamingServer(array(
5555
* new LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
5656
* new RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
5757
* new RequestBodyParserMiddleware(),
5858
* new LimitConcurrentRequestsMiddleware(1), // only execute 1 handler (no concurrency)
5959
* $handler
60-
* ]));
60+
* ));
6161
* ```
6262
*
6363
* @see RequestBodyBufferMiddleware

src/StreamingServer.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,16 +93,18 @@ class StreamingServer extends EventEmitter
9393
* connections in order to then parse incoming data as HTTP.
9494
* See also [listen()](#listen) for more details.
9595
*
96-
* @param callable $callback
96+
* @param callable|callable[] $requestHandler
9797
* @see self::listen()
9898
*/
99-
public function __construct($callback)
99+
public function __construct($requestHandler)
100100
{
101-
if (!is_callable($callback)) {
101+
if (is_array($requestHandler)) {
102+
$requestHandler = new MiddlewareRunner($requestHandler);
103+
} elseif (!is_callable($requestHandler)) {
102104
throw new \InvalidArgumentException();
103105
}
104106

105-
$this->callback = $callback;
107+
$this->callback = $requestHandler;
106108
}
107109

108110
/**

tests/FunctionalServerTest.php

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Psr\Http\Message\ServerRequestInterface;
66
use React\Http\Middleware\LimitConcurrentRequestsMiddleware;
77
use React\Http\Middleware\RequestBodyBufferMiddleware;
8-
use React\Http\MiddlewareRunner;
98
use React\Socket\Server as Socket;
109
use React\EventLoop\Factory;
1110
use React\Http\StreamingServer;
@@ -47,42 +46,16 @@ public function testPlainHttpOnRandomPort()
4746
$socket->close();
4847
}
4948

50-
public function testPlainHttpOnRandomPortWithMiddlewareRunner()
49+
public function testPlainHttpOnRandomPortWithSingleRequestHandlerArray()
5150
{
5251
$loop = Factory::create();
5352
$connector = new Connector($loop);
5453

55-
$server = new StreamingServer(new MiddlewareRunner(array(function (RequestInterface $request) {
56-
return new Response(200, array(), (string)$request->getUri());
57-
})));
58-
59-
$socket = new Socket(0, $loop);
60-
$server->listen($socket);
61-
62-
$result = $connector->connect($socket->getAddress())->then(function (ConnectionInterface $conn) {
63-
$conn->write("GET / HTTP/1.0\r\nHost: " . noScheme($conn->getRemoteAddress()) . "\r\n\r\n");
64-
65-
return Stream\buffer($conn);
66-
});
67-
68-
$response = Block\await($result, $loop, 1.0);
69-
70-
$this->assertContains("HTTP/1.0 200 OK", $response);
71-
$this->assertContains('http://' . noScheme($socket->getAddress()) . '/', $response);
72-
73-
$socket->close();
74-
}
75-
76-
public function testPlainHttpOnRandomPortWithEmptyMiddlewareRunner()
77-
{
78-
$loop = Factory::create();
79-
$connector = new Connector($loop);
80-
81-
$server = new StreamingServer(new MiddlewareRunner(array(
54+
$server = new StreamingServer(array(
8255
function () {
8356
return new Response(404);
8457
},
85-
)));
58+
));
8659

8760
$socket = new Socket(0, $loop);
8861
$server->listen($socket);
@@ -724,7 +697,7 @@ public function testLimitConcurrentRequestsMiddlewareRequestStreamPausing()
724697
$loop = Factory::create();
725698
$connector = new Connector($loop);
726699

727-
$server = new StreamingServer(new MiddlewareRunner(array(
700+
$server = new StreamingServer(array(
728701
new LimitConcurrentRequestsMiddleware(5),
729702
new RequestBodyBufferMiddleware(16 * 1024 * 1024), // 16 MiB
730703
function (ServerRequestInterface $request, $next) use ($loop) {
@@ -737,7 +710,7 @@ function (ServerRequestInterface $request, $next) use ($loop) {
737710
function (ServerRequestInterface $request) {
738711
return new Response(200, array(), (string)strlen((string)$request->getBody()));
739712
}
740-
)));
713+
));
741714

742715
$socket = new Socket(0, $loop);
743716
$server->listen($socket);

tests/StreamingServerTest.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22

33
namespace React\Tests\Http;
44

5-
use React\Http\MiddlewareRunner;
6-
use React\Http\StreamingServer;
75
use Psr\Http\Message\ServerRequestInterface;
86
use React\Http\Response;
9-
use React\Stream\ThroughStream;
7+
use React\Http\StreamingServer;
108
use React\Promise\Promise;
9+
use React\Stream\ThroughStream;
1110

1211
class StreamingServerTest extends TestCase
1312
{
@@ -96,14 +95,14 @@ public function testRequestEvent()
9695
$this->assertSame('127.0.0.1', $serverParams['REMOTE_ADDR']);
9796
}
9897

99-
public function testRequestEventWithMiddlewareRunner()
98+
public function testRequestEventWithSingleRequestHandlerArray()
10099
{
101100
$i = 0;
102101
$requestAssertion = null;
103-
$server = new StreamingServer(new MiddlewareRunner(array(function (ServerRequestInterface $request) use (&$i, &$requestAssertion) {
102+
$server = new StreamingServer(array(function (ServerRequestInterface $request) use (&$i, &$requestAssertion) {
104103
$i++;
105104
$requestAssertion = $request;
106-
})));
105+
}));
107106

108107
$this->connection
109108
->expects($this->any())

0 commit comments

Comments
 (0)