-
-
Notifications
You must be signed in to change notification settings - Fork 160
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
Middleware #213
Conversation
Would it make sense- in terms of backwards compatibility- to allow the server to apply a bit of magic? If the parameter is a |
@andig that does make a lot of sense to me, I'll update the PR with that later today 👍 |
src/MiddlewareStack.php
Outdated
|
||
namespace React\Http; | ||
|
||
use Interop\Http\ServerMiddleware\MiddlewareInterface; |
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.
Must be React\Http\MiddlewareInterface
.
src/MiddlewareInterface.php
Outdated
* @param ServerRequestInterface $request | ||
* @param MiddlewareStackInterface $stack | ||
* | ||
* @return PromiseInterface<ResponseInterface> |
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.
Can also return a response directly: ResponseInterface|PromiseInterface<ResponseInterface>
src/Server.php
Outdated
} | ||
|
||
$this->callback = $callback; | ||
throw new \InvalidArgumentException(); |
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.
Exception message missing explaining what types of arguments can be given to the constructor.
src/Server.php
Outdated
} | ||
|
||
$this->callback = $callback; | ||
throw new \InvalidArgumentException('Only MiddlewareInterface[] or MiddlewareStackInterface implementations are supported'); |
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.
Would not it be better to use fully qualified names?
…apped into a middleware stack; restoring old behavior while still supporting middleware
Passing a Callback without array is not supported yet. But maybe that case isn‘t that important after all. I really like the original, simple callable stile. Thank you! |
@andig it is, I've reverted that change and you can now pass in a callable, an array with middlewares or a middleware stack to handle your request. |
src/Server.php
Outdated
$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); | ||
}); |
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.
Since MiddlewareStack::process()
always returns a promise, this can be simplified to just
$cancel = $middlewareStack->process($request);
Note, that catching exceptions should be moved to the MiddlewareStack
, see #213 (comment)
src/MiddlewareStack.php
Outdated
) | ||
) | ||
); | ||
} |
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.
To prevent exceptions thrown from process()
bubbling out from here, this should be wrapped like this to turn them into rejections:
return new Promise\Promise(function ($resolve, $reject) use ($request, $middlewares, $middleware) {
$response = $middleware->process(
$request,
new self(
$this->defaultResponse,
$middlewares
)
);
$resolve($response);
});
…ck; also forward cancels correctly
@jsor Updated with your suggestions, had a lot of fun with the cancellation forwarding |
… runners/stacks) are callables. As a result the Callback middleware has been removed as it has become obsolete
src/Server.php
Outdated
* ); | ||
* }); | ||
* $server = new Server([ | ||
* new Callback(function (ServerRequestInterface $request) { |
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.
Still using Callback
removed in b4211aa
$middlewares = [ | ||
new LimitHandlers(5), | ||
new Buffer(), | ||
new Callback(function (ServerRequestInterface $request) { |
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.
Still using Callback
removed in b4211aa
F438
README.md
return new Response(200); | ||
}), | ||
]; | ||
``` |
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.
Leftover from b4211aa
examples/12-middleware.php
Outdated
$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) { |
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.
Still using Callback
removed in b4211aa
examples/13-request-sec.php
Outdated
new Response(500), | ||
array( | ||
new Incre($counts), | ||
new Callback(function (ServerRequestInterface $request) use ($loop) { |
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.
Still using Callback
removed in b4211aa
tests/FunctionalServerTest.php
Outdated
@@ -2,6 +2,8 @@ | |||
|
|||
namespace React\Tests\Http; | |||
|
|||
use React\Http\Middleware\Buffer; | |||
use React\Http\Middleware\Callback; |
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.
Unused use's?
src/Server.php
Outdated
$promise = new Promise(function ($resolve, $reject) use ($callback, $request, &$cancel) { | ||
$cancel = $callback($request); | ||
$promise = new Promise\Promise(function ($resolve, $reject) use ($request, &$cancel) { | ||
$callable = $this->callable; |
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.
$this
isn't available in PHP 5.3, see https://travis-ci.org/reactphp/http/jobs/271815811
src/MiddlewareStack.php
Outdated
$middlewares | ||
) | ||
); | ||
$cancel->done($resolve, $reject); |
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.
Since react/promise v1.x is supported, done()
can't be used as its only available since v2.0. See https://travis-ci.org/reactphp/http/jobs/271815812
src/Server.php
Outdated
$callable = $this->callable; | ||
$cancel = $callable($request); | ||
if ($cancel instanceof Promise\CancellablePromiseInterface) { | ||
$cancel->done($resolve, $reject); |
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.
No done()
because react/promise v1.x.
Updated the PR (except documentation, examples and readme) turning all middlewares into invokables and also the MiddlewareStack. The idea behind it is that it doesn't require any code changes and thus is backwards compatible from the consumer end. Whilest still allowing us to add middleware Ping @clue |
@jsor updated your points and the examples |
$that = $this; | ||
$cancel = null; | ||
return new Promise\Promise(function ($resolve, $reject) use ($middleware, $request, $middlewares, &$cancel, $that) { | ||
$cancel = $middleware( |
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.
Since we're not working on an interface anymore (but on plain callables
), there must be a check if $cancel
is actually a promise (or not a Response
).
Extracted the Middleware Runner into #215 |
Extracted the Buffer middleware into #216 |
Let me start with saying awesome! 🎉 It's really nice to see how our discussion and our very quick prototype at this year's DPC turned out in this PR! I'd love to get this feature in ASAP, but this PR contains quite a few changes, so it's really hard to review in its current state. I see you've started splitting this into smaller PRs (#215 and #216), which I think makes perfect sense, so let's get this rolling! |
|
||
#### Callback | ||
|
||
A few build in middlewares are shipped with this package, one of them is |
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
?
|
||
#### Buffer | ||
|
||
Another build in middleware is `Buffer` which will buffer the incoming |
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
?
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.
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.
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.
Done 👍
@WyriHaximus thanks for all your efforts. Looking at react/http from consumer side I've been wondering why you're not using PSR-15? |
@andig One of the reasons is that PSR-15 requires a response from the middleware where we return either a response or a promise (we wrap the response in a promise anyway). Also going with |
FYI here is an example of a project where I'm using that adapter: $middleware = [
new PSR15Middleware($loop, Uuid::class),
new PSR15Middleware($loop, ClientIp::class),
new PSR15Middleware($loop, AccessLog::class, [$logger], function ($accessLog) {
return $accessLog->context(function (ServerRequestInterface $request, ResponseInterface $response) {
return [
'client-ip' => $request->getAttribute('client-ip'),
'request-id' => $request->getHeaderLine('X-Uuid'),
'response-time' =>$response->getHeaderLine('X-Response-Time'),
];
});
}),
new PSR15Middleware($loop, ResponseTime::class),
new WithHeadersMiddleware([
'X-Powered-By' => 'wyrimaps.net-static-maps-server (' . $version . ')',
]),
new WithRandomHeadersMiddleware(Horde::headers(), 1, 2),
new RequestBodyBufferMiddleware(),
new CustomRequestBodyParsers(),
new ClearBodyMiddleware(),
new PSR15Middleware($loop, Expires::class, [$expires]),
new PSR15Middleware($loop, Redirect::class, [[
'/' => 'https://wyrimaps.net/',
'/favicon.ico' => 'https://wyrimaps.net/favicon.ico',
]]),
new PSR15Middleware($loop, Robots::class, [false]),
new PSR15Middleware($loop, FastRoute::class, [$dispatcher]),
new PSR15Middleware($loop, RequestHandler::class),
];
$server = new HttpServer(new MiddlewareRunner($middleware)); |
During DPC @clue and I had a long chat about how to handle body parsers without loosing the current flexibility. We came to the conclusion that middlewares are the way to go. The following proposal is inspired by the WIP PSR-15 but doesn't implement it (I'll get back on that later in this PR).
Suggested reading order
This PR contains a lot of changes, how ever most of those changes are in examples and tests. Here is a recommended reading order:
Major changes
Where
0.7
only requires you to pass acallable
to handle incoming requests, this PR proposes to use middleware for that. (While still leaving thecallable
way intact by magically wrapping it.) One way to setup middleware by passing an array with middlewares implementing MiddlewareInterface:Or by passing in a concrete implementation of MiddlewareStackInterface:
The latter is done automatically when doing the former internally in the server. But you can create your own middleware stack implementation and use that instead.
Included middlewares
This PR includes three middlewares
Buffer
,Callback
, andLimitHandlers
.Buffer
Buffers the request body until it is fully in or when it reach the given size:
Callback
Handles request just like
0.7
using a callback:LimitHandlers
Limits the number of concurrent request being handled at the same time:
Body parsing
Currently one of our main issues with body parsing is the memory usage. But with the
LimitHandlers
we can pause the incoming request stream until we're ready handle it thus limiting the amount of memory needed to handle request. And withBuffer
we can first stream the body in, parse it with for exampleBodyParser
, and then hand it to the next middleware on the stack.PSR-15
While the middleware implementation in this PR is inspired by the work done for the coming PSR-15 it doesn't implement it due to a small but major different. This implementation relies on promises where PSR-15 always assumes a response to be returned. An adapter for PSR-15 is beyond the scope of this PR but not for a Friends of ReactPHP packages and thus I've 8000 created
for/http-middleware-psr15-adapter
that utilizes RecoilPHP and on the fly PHP parsing and rewriting of PSR-15 middlewares.With that package you can wrap the adapter around a PSR-15 as follows:
*Note: I've added last section to show how easy it can be to add PSR-15 middlewares, with the rewriting there is no guarantee for success