Skip to content

Commit 23e1470

Browse files
authored
Merge pull request #23 from clue/global-middleware
Add support for global middleware
2 parents f79feb6 + e1b9551 commit 23e1470

File tree

4 files changed

+370
-47
lines changed

4 files changed

+370
-47
lines changed

docs/api/middleware.md

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ $app->get(
3636
},
3737
function (Psr\Http\Message\ServerRequestInterface $request) {
3838
$role = $request->getAttribute('admin') ? 'admin' : 'user';
39-
return new React\Http\Message\Response(200, [], "hello $role!");
39+
return new React\Http\Message\Response(200, [], "Hello $role!\n");
4040
}
4141
);
4242
```
@@ -58,6 +58,7 @@ like this:
5858

5959
namespace Acme\Todo;
6060

61+
use Psr\Http\Message\ResponseInterface;
6162
use Psr\Http\Message\ServerRequestInterface;
6263

6364
class DemoMiddleware
@@ -72,7 +73,7 @@ class DemoMiddleware
7273

7374
// call next handler in chain
7475
$response = $next($request);
75-
assert($response instanceof Psr\Http\Message\ResponseInterface);
76+
assert($response instanceof ResponseInterface);
7677

7778
// optionally modify response before returning to previous handler
7879
// $response = $response->withHeader('Content-Type', 'text/plain');
@@ -136,13 +137,14 @@ class AdminMiddleware
136137
namespace Acme\Todo;
137138

138139
use Psr\Http\Message\ServerRequestInterface;
140+
use React\Http\Message\Response;
139141

140142
class UserController
141143
{
142144
public function __invoke(ServerRequestInterface $request)
143145
{
144146
$role = $request->getAttribute('admin') ? 'admin' : 'user';
145-
return new React\Http\Message\Response(200, [], "hello $role!");
147+
return new Response(200, [], "Hello $role!\n");
146148
}
147149
}
148150
```
@@ -156,7 +158,7 @@ use Acme\Todo\UserController;
156158

157159
// …
158160

159-
$app->get('/user/', new AdminMiddleware(), new UserController());
161+
$app->get('/user', new AdminMiddleware(), new UserController());
160162
```
161163

162164
For example, an HTTP `GET` request for `/user` would first call the middleware handler which then modifies this request and passes the modified request to the next controller function.
@@ -172,7 +174,7 @@ Likewise, we can add an example middleware handler that can modify the outgoing
172174

173175
namespace Acme\Todo;
174176

175-
use Psr\Http\Message\ResponseInterface);
177+
use Psr\Http\Message\ResponseInterface;
176178
use Psr\Http\Message\ServerRequestInterface;
177179

178180
class ContentTypeMiddleware
@@ -194,12 +196,13 @@ class ContentTypeMiddleware
194196
namespace Acme\Todo;
195197

196198
use Psr\Http\Message\ServerRequestInterface;
199+
use React\Http\Message\Response;
197200

198201
class UserController
199202
{
200203
public function __invoke(ServerRequestInterface $request)
201204
{
202-
return new React\Http\Message\Response(200, [], "Hello world!\n");
205+
return new Response(200, [], "Hello world!\n");
203206
}
204207
}
205208
```
@@ -213,7 +216,7 @@ use Acme\Todo\UserController;
213216

214217
// …
215218

216-
$app->get('/user/', new ContentTypeMiddleware(), new UserController());
219+
$app->get('/user', new ContentTypeMiddleware(), new UserController());
217220
```
218221

219222
For example, an HTTP `GET` request for `/user` would first call the middleware handler which passes on the request to the controller function and then modifies the response that is returned by the controller function.
@@ -242,15 +245,10 @@ As a consequence, each middleware handler can also return
242245
243246
## Global middleware
244247

245-
> ℹ️ **Feature preview**
246-
>
247-
> This is a feature preview, i.e. it might not have made it into the current beta.
248-
> Give feedback to help us prioritize.
249-
250-
Additionally, you can also add middleware to the `App` object itself to register
251-
a global middleware handler for all registered routes:
248+
Additionally, you can also add middleware to the [`App`](app.md) object itself
249+
to register a global middleware handler:
252250

253-
```php hl_lines="8"
251+
```php hl_lines="7"
254252
# app.php
255253
<?php
256254

@@ -264,6 +262,9 @@ $app->get('/user', new UserController());
264262
$app->run();
265263
```
266264

265+
Any global middleware handler will always be called for all registered routes
266+
and also any requests that can not be routed.
267+
267268
You can also combine global middleware handlers (think logging) with additional
268269
middleware handlers for individual routes (think authentication).
269270
Global middleware handlers will always be called before route middleware handlers.

src/App.php

Lines changed: 74 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,45 @@
2121
class App
2222
{
2323
private $loop;
24+
private $middleware;
2425
private $router;
2526
private $routeDispatcher;
2627

27-
public function __construct(LoopInterface $loop = null)
28+
/**
29+
* Instantiate new X application
30+
*
31+
* ```php
32+
* // instantiate
33+
* $app = new App();
34+
*
35+
* // instantiate with global middleware
36+
* $app = new App($middleware);
37+
* $app = new App($middleware1, $middleware2);
38+
*
39+
* // instantiate with optional $loop
40+
* $app = new App($loop);
41+
* $app = new App($loop, $middleware);
42+
* $app = new App($loop, $middleware1, $middleware2);
43+
*
44+
* // invalid $loop argument
45+
* $app = new App(null);
46+
* $app = new App(null, $middleware);
47+
* ```
48+
*
49+
* @param callable|LoopInterface|null $loop
50+
* @param callable ...$middleware
51+
* @throws \TypeError if given $loop argument is invalid
52+
*/
53+
public function __construct($loop = null, callable ...$middleware)
2854
{
29-
if ($loop === null) {
30-
$loop = Loop::get();
55+
if (\is_callable($loop)) {
56+
\array_unshift($middleware, $loop);
57+
$loop = null;
58+
} elseif (\func_num_args() !== 0 && !$loop instanceof LoopInterface) {
59+
throw new \TypeError('Argument 1 ($loop) must be callable|' . LoopInterface::class . ', ' . $this->describeType($loop) . ' given');
3160
}
32-
$this->loop = $loop;
61+
$this->loop = $loop ?? Loop::get();
62+
$this->middleware = $middleware;
3363
$this->router = new RouteCollector(new RouteParser(), new RouteGenerator());
3464
}
3565

@@ -315,6 +345,45 @@ private function sendResponse(ServerRequestInterface $request, ResponseInterface
315345
* turned into a valid error response before returning.
316346
*/
317347
private function handleRequest(ServerRequestInterface $request)
348+
{
349+
$handler = function (ServerRequestInterface $request) {
350+
return $this->routeRequest($request);
351+
};
352+
if ($this->middleware) {
353+
$handler = new MiddlewareHandler(array_merge($this->middleware, [$handler]));
354+
}
355+
356+
try {
357+
$response = $handler($request);
358+
} catch (\Throwable $e) {
359+
return $this->errorHandlerException($e);
360+
}
361+
362+
if ($response instanceof \Generator) {
363+
$response = $this->coroutine($response);
364+
}
365+
366+
if ($response instanceof ResponseInterface) {
367+
return $response;
368+
} elseif ($response instanceof PromiseInterface) {
369+
return $response->then(function ($response) {
370+
if (!$response instanceof ResponseInterface) {
371+
return $this->errorHandlerResponse($response);
372+
}
373+
return $response;
374+
}, function ($e) {
375+
if ($e instanceof \Throwable) {
376+
return $this->errorHandlerException($e);
377+
} else {
378+
return $this->errorHandlerResponse(\React\Promise\reject($e));
379+
}
380+
});
381+
} else {
382+
return $this->errorHandlerResponse($response);
383+
}
384+
}
385+
386+
private function routeRequest(ServerRequestInterface $request)
318387
{
319388
if (\strpos($request->getRequestTarget(), '://') !== false || $request->getMethod() === 'CONNECT') {
320389
return $this->errorProxy($request);
@@ -342,34 +411,7 @@ private function handleRequest(ServerRequestInterface $request)
342411
$request = $request->withAttribute($key, rawurldecode($value));
343412
}
344413

345-
try {
346-
$response = $handler($request);
347-
} catch (\Throwable $e) {
348-
return $this->errorHandlerException($e);
349-
}
350-
351-
if ($response instanceof \Generator) {
352-
$response = $this->coroutine($response);
353-
}
354-
355-
if ($response instanceof ResponseInterface) {
356-
return $response;
357-
} elseif ($response instanceof PromiseInterface) {
358-
return $response->then(function ($response) {
359-
if (!$response instanceof ResponseInterface) {
360-
return $this->errorHandlerResponse($response);
361-
}
362-
return $response;
363-
}, function ($e) {
364-
if ($e instanceof \Throwable) {
365-
return $this->errorHandlerException($e);
366-
} else {
367-
return $this->errorHandlerResponse(\React\Promise\reject($e));
368-
}
369-
});
370-
} else {
371-
return $this->errorHandlerResponse($response);
372-
}
414+
return $handler($request);
373415
}
374416
} // @codeCoverageIgnore
375417

0 commit comments

Comments
 (0)