生命周期 8--request 对象是如何通过路由&控制器中间件的
前言
上一节已经分析了《如何根据route对象的信息获取路由和控制器中间件》,本文分析一下「request对象是如何通过路由中间件的」
代码位置
在route对象的runRouteWithinStack方法中,\Illuminate\Routing\Router::runRouteWithinStack
。
/**
* Run the given route within a Stack "onion" instance.
*
* @param \Illuminate\Routing\Route $route
* @param \Illuminate\Http\Request $request
* @return mixed
*/
protected function runRouteWithinStack(Route $route, Request $request)
{
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
//前面已经获取好的路由、控制器中间件
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}
要分析的代码
(new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
这种骚操作已经不是头一次见了,早在《管道流分析》中,就已经见识了。
看看是不是几乎一样的?
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
不同的是:前面的中间件是全局中间件,而当前的中间件是路由、控制器的中间件。然后前面的最终操作是发送request到路由,目前的最终操作是执行路由的操作并返回最终的response对象。
由于篇幅限制,本文将集中在request对象如何穿过路由&控制器中间件。
再看看将要穿过哪些中间件:
array (
0 => 'App\\Http\\Middleware\\EncryptCookies',
1 => 'Illuminate\\Routing\\Middleware\\SubstituteBindings',
2 => 'Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse',
3 => 'Illuminate\\Session\\Middleware\\StartSession',
4 => 'Illuminate\\View\\Middleware\\ShareErrorsFromSession',
5 => 'App\\Http\\Middleware\\VerifyCsrfToken',
6 => 'App\\Http\\Middleware\\RecordLastActivedTime',
7 => 'App\\Http\\Middleware\\RedirectIfAuthenticated',
)
具体分析
迭代创建超级大闭包
迭代开始前,指定目标,此为$destination
function ($request) use ($route) {
return $this->prepareResponse($request, $route->run())
而$this->prepareDestination($destination)
就返回闭包0(这是为了讲述方便自己设定的)
/**
* Get the final piece of the Closure onion.
*
* @param \Closure $destination
* @return \Closure
*/
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
return $destination($passable);
};
}
因此,闭包0是一个双层闭包,可以看成是将要执行$this->prepareResponse($request, $route->run())
。
第1次迭代:将返回一个闭包对象1(简称闭包1,下同)。如下:
function ($passable) use ($stack, $pipe) {
try {
$slice = parent::carry();
$callable = $slice($stack, $pipe);
return $callable($passable);
} catch (Exception $e) {
return $this->handleException($passable, $e);
} catch (Throwable $e) {
return $this->handleException($passable, new FatalThrowableError($e));
}
};
//其中$stack为闭包0。
$stack = function ($passable) use ($destination) {
return $destination($passable);
};
$pipe = "App\Http\Middleware\RedirectIfAuthenticated";
第2次迭代:将返回一个闭包2。如下:
function ($passable) use ($stack, $pipe) {
//omit for clarity.
};
//其中$stack为闭包1,闭包1的$stack是闭包0.
第3次迭代:将返回一个闭包3。如下:
function ($passable) use ($stack, $pipe) {
//omit for clarity.
};
//其中$stack为闭包2,闭包2中的$stack又是闭包1,闭包1的$stack是闭包0.
第4次迭代:将返回一个闭包4。如下:
function ($passable) use ($stack, $pipe) {
//omit for clarity.
};
//其中$stack为闭包3,闭包3中的$stack又是闭包2,闭包2中的$stack又是闭包1,闭包1的$stack是闭包0.
依次类推。。。
第n次迭代:将返回一个闭包n
。如下:
function ($passable) use ($stack, $pipe) {
//omit for clarity.
};
//其中$stack为闭包n,闭包n中的$stack又是闭包n-1......闭包1的$stack是闭包0.
最后,$pipeline
就是一个闭包,它的$stack
就是闭包n
.
function ($passable) use ($stack, $pipe) {
try {
$slice = parent::carry();
$callable = $slice($stack, $pipe);
return $callable($passable);
} catch (Exception $e) {
return $this->handleException($passable, $e);
} catch (Throwable $e) {
return $this->handleException($passable, new FatalThrowableError($e));
}
};
穿过中间件
简要过程
执行$pipeline($this->passable)
操作,会先执行$slice = parent::carry();
,此操作返回一个方法签名为$stack, $pipe
的闭包A
,赋给变量$slice
。
然后执行$callable = $slice($stack, $pipe);
,在这里,将上面的闭包n传入$slice
,执行它,而它仍然返回一个闭包B
,方法签名为$passable
,且use了$stack, $pipe
。返回后赋值给
变量$callable
。
$callable($passable)
,就是去执行闭包B
。在这个过程中,$stack
是闭包n
。而这个$stack
会传入第一个$pipe
的handle
方法$next
参数中,因此在执行完handle
方法中的处理$request
的代码后,$next(...)
就是调用闭包n。
而闭包n的代码结构跟$pipeline
闭包的代码结构是完全一致的,唯一的不同就是$stack, $pipe
的不同: 闭包n中的$stack是闭包n-1,这个前面已经提到过了。
因此,在下一次handle方法中,$next
就是闭包n-1, $next(...)
就是调用闭包n-1。以此类推,这样就不断的调用嵌套的闭包,直到最里面的闭包0。而闭包0是一个双层闭包,可以看成是将要执行$this->prepareResponse($request, $route->run())
。
详细过程
首先穿过的是App\Http\Middleware\EncryptCookies
中间件,其handle方法是:
public function handle($request, Closure $next)
{
//先将request中的cookie解密,然后进入下一个中间件
//然后后面返回的结果再加密,返回给response
return $this->encrypt($next($this->decrypt($request)));
}
注意:这里只解密,然后就进入下一个中间件了,还没有返回,因此暂时不加密。
然后是Illuminate\Session\Middleware\StartSession
中间件:
public function handle($request, Closure $next)
{
$this->sessionHandled = true;
// If a session driver has been configured, we will need to start the session here
// so that the data is ready for an application. Note that the Laravel sessions
// do not make use of PHP "native" sessions in any way since they are crappy.
if ($this->sessionConfigured()) {
$request->setLaravelSession(
$session = $this->startSession($request)
);
$this->collectGarbage($session);
}
$response = $next($request);
// Again, if the session has been configured we will need to close out the session
// so that the attributes may be persisted to some storage medium. We will also
// add the session identifier cookie to the application response headers now.
if ($this->sessionConfigured()) {
$this->storeCurrentUrl($request, $session);
$this->addCookieToResponse($response, $session);
}
return $response;
}
可以看到这里,这里也是next后还有其他操作,这种叫做后置中间件,官网有提到。
然后穿过剩下的中间件,到达目的地,准备执行router对象的prepareResponse方法:
return $this->prepareResponse(
$request, $route->run()
);
router对象的prepareResponse方法如下:
/**
* Create a response instance from the given value.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @param mixed $response
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
public function prepareResponse($request, $response)
{
return static::toResponse($request, $response);
}
传入当前的request对象和执行当前route对象的run方法后的结果,最后返回一个response对象。
堆栈视角
从IDE的堆栈中看得更清楚,穿过顺序从下往上,可以看到到底经过哪些中间件,找handle()就可以了。
Route.php:165, Illuminate\Routing\Route->run()
Router.php:679, Illuminate\Routing\Router->Illuminate\Routing\{closure}()
Pipeline.php:30, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
RedirectIfAuthenticated.php:24, App\Http\Middleware\RedirectIfAuthenticated->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
RecordLastActivedTime.php:24, App\Http\Middleware\RecordLastActivedTime->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
VerifyCsrfToken.php:75, App\Http\Middleware\VerifyCsrfToken->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
AddQueuedCookiesToResponse.php:37, Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
SubstituteBindings.php:41, Illuminate\Routing\Middleware\SubstituteBindings->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
ShareErrorsFromSession.php:49, Illuminate\View\Middleware\ShareErrorsFromSession->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
StartSession.php:63, Illuminate\Session\Middleware\StartSession->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
EncryptCookies.php:66, App\Http\Middleware\EncryptCookies->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
Pipeline.php:104, Illuminate\Routing\Pipeline->then()
Router.php:681, Illuminate\Routing\Router->runRouteWithinStack()
Router.php:656, Illuminate\Routing\Router->runRoute()
Router.php:622, Illuminate\Routing\Router->dispatchToRoute()
Router.php:611, Illuminate\Routing\Router->dispatch()
Kernel.php:176, App\Http\Kernel->Illuminate\Foundation\Http\{closure}()
Pipeline.php:30, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
InjectDebugbar.php:58, Barryvdh\Debugbar\Middleware\InjectDebugbar->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
TrustProxies.php:57, App\Http\Middleware\TrustProxies->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
TransformsRequest.php:31, Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
TransformsRequest.php:31, App\Http\Middleware\TrimStrings->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
ValidatePostSize.php:27, Illuminate\Foundation\Http\Middleware\ValidatePostSize->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
CheckForMaintenanceMode.php:62, App\Http\Middleware\CheckForMaintenanceMode->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
Request.php:111, Dingo\Api\Http\Middleware\Request->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
Pipeline.php:104, Illuminate\Routing\Pipeline->then()
Kernel.php:151, App\Http\Kernel->sendRequestThroughRouter()
Kernel.php:116, App\Http\Kernel->handle()
index.php:55, {main}()
计划
下一节将描述通过执行当前route对象的run方法,如何执行路由对应的方法并返回结果。这也是我们刚开始使用框架时大部分代码书写的位置。
小结
- 中间件分为前置中间件和后置中间件,仅仅在于
$next(...)
前后是否有相应的处理逻辑代码。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: