Closed klimov-paul closed 5 years ago
PSR-15 will be voted for in January: https://twitter.com/nauleyco/status/946880932706840576
So we should rely on final "http-interop/http-server-middleware": "^1.0.1".
package.
It would be good to check https://framework.zend.com/blog/2017-12-14-expressive-3-dev.html as well.
Looking at MiddlewareInterface::process()
I am not sure in which way it should be integrated.
This method looks like Middleware handler always produces a final response to be send back to the client.
// we have PSR-compatible server request passing to handler and gain response in return without any conditions or other posibilities
$psrResponse = $middleware->process(Yii::$app->request, $someMiddlewareRequestHandler);
It looks like middleware is some final request handler. At least several middlewares can not be processed in the chain or stack. Each middleware handler call produces brand new response object and PSR does not provide any way to merge them.
All this does not look matching 'middleware' term to me. What exactly do I miss?
@klimov-paul It works like:
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) {
// do something with $request
$response = $handler->process($request, $this->getNext());
// do something with response
return $response;
}
So it looks like onion - only middleware in the middle creates new response.
So what? It still means that 2 separated handlers can not be run in chain. I can not setup 2 independent middlewares from different sources to handle sole request.
In the Yii scope PSR middleware is particlar implementation of controller action, which handlers incoming web request and generates web response. While I expect it to function like action filters, which can be applied for module, application or controller, and being independent allowing multiple middleware to function.
Could someone post a link for several 3ed party middleware implementations, which are supposed to be integrated into PSR-7 compatible web application? Perhaps while reviewing the most popular middlewares I can understand the whole meaning of it.
From what I see now, the middleware Yii integration is the matter of creation of predefined Action clases, in the following way:
class MiddlewareAction extends \yii\base\Action
{
public $handler;
public $middleware;
public function run()
{
$middleware = Instance::ensure($this->middleware, MiddlewareInterface::class);
$handler = Instance::ensure($this->handler, RequestHandlerInterface::class);
return $middleware->process(Yii::$app->getRequest(), $handler);
}
}
class RequestHandlerAction extends \yii\base\Action
{
public $handler;
public function run()
{
$handler = Instance::ensure($this->handler, RequestHandlerInterface::class);
return $handler->handle(Yii::$app->getRequest());
}
}
Such actions should function fine with 2.1 branch.
It still means that 2 separated handlers can not be run in chain. I can not setup 2 independent middlewares from different sources to handle sole request.
You can do this with dispatcher that wraps separate middlewares and runs it in chain. See https://github.com/mindplay-dk/middleman/blob/master/src/Dispatcher.php
So, it simply looses the previous response from the chain as I can see, returning only the last one. Does not seem right, to be honest. However, this means solution I have proposed earlier should do just fine.
So, it simply looses the previous response from the chain
No, it's not. It returns response from previous middleware.
Can you point me to the place, where 2 responses from 2 different handlers are merged?
They're never merged, because there is no 2 responses.
In this case response is created in "view", passed to "LastVisited" middleware, which passes it to "Authentication", which passes it to "Session", which passes it to "Common". Each middleware can modify response by with*()
method, but this is still the same response (or at least clone of base response created in "view" - center of "onion").
This means particular handler should be designed in the mean of usage another handler inside it. E.g. this will work only particular middleware is aware of another middleware in the chain, creating explicit dependency. I would say this is not very practical, but it also means we should not implement middleware stack or chain inside the framework- that makes our life easier. So indeed all we need is creation of 2 dedicated actions and perhaps an action filter.
This means particular handler should be designed in the mean of usage another handler inside it. E.g. this will work only particular middleware is aware of another middleware in the chain, creating explicit dependency.
You're wrong. You have a working solution (middleman) that is able to create chain with random middlewares - try to read its code more carefully or at least run it.
You're wrong. You have a working solution (middleman) that is able to create chain with random middlewares - try to read its code more carefully or at least run it.
Sorry but I can not see it. THe only assumption (or clue) which I can imagine i that particular RequestHandlerInterface
instance may return null
instead of Response
instance indicating that request should proceed further. Otherwise middleware stack calls are impossible without response loss.
I think I understand. So $handler
at Middleware::process()
method is supposed to be a fallback, which is called only in case Middleware can not resolve request on its own. E.g.:
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler)
{
if (some condition) {
$response = new MiddlewareResponse();
// ....
retrun $response;
}
return $handler->handle($request);
}
Thus middleman
creates an atrificial wrapper matching RequestHandlerInterface
around any MiddlewareInterface
instance passed in its stack.
Jesus, this is a insanity. Only some Symfony-infested mind could create just a twisted solution for the simpliest task.
Summarizing: Middleware is just an analog for the current action filters system, which uses callback-like approach instead of events for the same goal.
E.g. RequestHandlerInterface
should wrap Application::runAction()
and be passed to developer-defined middleware.
Particular middleware may either compose its own response and use passed handler as a fallback (matches ActionFilter::beforeAction()
) or invoke handler and modify response created by it (matches ActionFilter::afterAction()
).
Although middleware can be put inside ActionFilter
it will unlikely be an acceptable solution, because in this case developer will have to decide whether to run middleware at ActionFilter::beforeAction()
or ActionFilter::afterAction()
.
Thus it seems that current Yii filter system should be dropped and rewritten as middleware. However, this eliminates the ability of usage action filters for the console requests.
Relates to #15071, #13922, #10659
@klimov-paul, It is just functional approach.
Let's take a simple action:
$action = function ($request) {
return new HtmlResponse('Hello!');
};
$response = $action($request);
For example, we need to add authentication and profiler around of the action.
We can define some middleware. They look like decorators:
// Redirects all guests to login page
$auth = function ($request, callable $next) {
if (!$request->...) {
return new RedirectResponse('/login');
}
return $next($request);
}
// Runs the next middleware/action and merges custom header to its response
$profiler = function ($request, callable $next) {
$start = microtime(true);
$response = $next($request);
$stop = microtime(true);
return $response->withHeader('X-Profiler-Time', $stop - $start);
}
And we can construct a pipe with nested $next
callbacks:
$pipeline = function ($request) {
return $profiler($request, function ($request) {
return $auth($request, function ($request) {
return $action($request);
});
});
};
$response = $pipeline($request);
Or by Object Oriented style we can write a pipeline object with internal recursion for simplier usage:
$pipeline = new Pipeline();
$pipeline->pipe(new ProfilerMiddleware());
$pipeline->pipe(new AuthMiddleware());
$pipeline->pipe(new HelloAction());
$response = $pipeline($request);
And more theory and code I showed on http://www.elisdn.ru/blog/115/psr7-framework-middleware screencast.
Solution proposed at https://github.com/yiisoft/yii2/pull/15459
Hi everyone!
My thoughts on the topic, example of use:
<?php
use PetrGrishin\Pipe\Pipe;
// Class name
$accessFiltres = [
AccessFilterMiddleware::class,
];
// Or class name with constructor arguments
$accessFiltres = [
[AccessFilterMiddleware::class, $paramMiddleware],
];
// Or closure function
$accessFiltres = [
function (Request $request, Responce $response, Closure $next) {
return $next($request, $response);
}
];
// Start the process
Pipe::create($request, $response)
->through($accessFiltres)
->through($XSSFiltres)
->then(function (Request $request, Responce $response) {
$response->runController($request);
});
Example middleware:
<?php
class AccessFilterMiddleware {
protected $paramMiddleware;
public function __construct($paramMiddleware = null) {
$this->paramMiddleware = $paramMiddleware;
}
public function __invoke(Request $request, Responce $response, Closure $next) {
if ($request->isPost()) {
$response->addError('Post is forbidden');
return false;
}
return $next($request, $response);
}
}
The implementation of the pipeline here https://github.com/petrgrishin/pipe/blob/master/src/Pipe.php
Introduce the suport for PSR-15 HTTP Middleware.
Proposed PSR draft: https://github.com/php-fig/fig-standards/tree/master/proposed/http-handlers
Unofficial, but widely-used, PSR implementation is the
http-interop/http-server-middleware
: https://github.com/http-interop/http-server-middlewareIn order to provide the support there should be abilty to setup handlers matching the
Psr\Http\Server\MiddlewareInterface
: