路由中间件之 StartSession
简介
[
'App\Http\Middleware\EncryptCookies',
'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse',
'Illuminate\Session\Middleware\StartSession',
'Illuminate\View\Middleware\ShareErrorsFromSession',
'App\Http\Middleware\VerifyCsrfToken',
'Illuminate\Routing\Middleware\SubstituteBindings',
'App\Http\Middleware\RedirectIfAuthenticated',
]
本章,我们围绕 StartSession 中间件进行分析和学习。
StartSession 的 handle 方法
public function handle($request, Closure $next)
{
// 记录 session 已启动
$this->sessionHandled = true;
if ($this->sessionConfigured()) {
$request->setLaravelSession(
$session = $this->startSession($request)
);
$this->collectGarbage($session);
}
// 此为分界线,以上是请求进来时的 session 处理,以下是回复响应时的 session 处理。
$response = $next($request);
if ($this->sessionConfigured()) {
$this->storeCurrentUrl($request, $session);
$this->addCookieToResponse($response, $session);
}
return $response;
}
请求进来时的 Session 处理
// 判断 session.php 配置文件有没有内容,有则执行 if 里面的代码
if ($this->sessionConfigured()) {
// 将 session 对象绑定到 request 对象上,方便后续处理
$request->setLaravelSession(
// 获取并处理 session 对象
$session = $this->startSession($request)
);
// 概率清除过期 session
$this->collectGarbage($session);
}
如何获取并处理 session 对象的呢
protected function startSession(Request $request)
{
// 从 getSession 方法中获取 session 对象
return tap($this->getSession($request), function ($session) use ($request) {
// 根据需要将 request 对象绑定到 session 对象上
$session->setRequestOnHandler($request);
// 根据前端 cookie 传递过来的 sessionId 在服务器上加载 session。
$session->start();
});
}
public function getSession(Request $request)
{
// 在 SessionManager 中,依据 session 配置的驱动,获取 session 对象
return tap($this->manager->driver(), function ($session) use ($request) {
// 从 cookie 中获取 sessionId
$session->setId($request->cookies->get($session->getName()));
});
}
上面两个方法的运行顺序--遵循由外至里的运行顺序,然后将结果再由里至外的方式传递回来。往外传递结果的时候,会运行 tap 函数里面的闭包,对 session 对象进行进一步加工。
那么最终的 session 对象到底是谁呢?我们来看一个方法就知道了
Illuminate\Session\SessionManager
protected function buildSession($handler)
{
if ($this->app['config']['session.encrypt']) {
return $this->buildEncryptedSession($handler);
}
// Store 对象就是最后返回的 session 对象了。
return new Store($this->app['config']['session.cookie'], $handler);
}
回复响应时的 session 处理
// 判断 session.php 配置文件有没有内容,有则执行 if 里面的代码
if ($this->sessionConfigured()) {
// 如果当前请求是 get 请求,且成功匹配到路由,且非ajax,则保存当前完整 url 至 session 对象中
$this->storeCurrentUrl($request, $session);
// 获取 sessionId 和 sessionName 添加至响应 cookie 给浏览器,实现与浏览器的记忆会话
$this->addCookieToResponse($response, $session);
}
storeCurrentUrl
protected function storeCurrentUrl(Request $request, $session)
{
// get请求、成功匹配路由、非ajax请求
if ($request->method() === 'GET' && $request->route() && ! $request->ajax()) {
// 保存 url
$session->setPreviousUrl($request->fullUrl());
}
}
addCookieToResponse
protected function addCookieToResponse(Response $response, Session $session)
{
// 判断 session 会话是否直接用的 cookie
if ($this->usingCookieSessions()) {
$this->manager->driver()->save();
}
// 判断 session 驱动是否为正确
if ($this->sessionIsPersistent($config = $this->manager->getSessionConfig())) {
// 设置 sessionId 至 cookie 中。
$response->headers->setCookie(new Cookie(
$session->getName(), $session->getId(), $this->getCookieExpirationDate(),
$config['path'], $config['domain'], $config['secure'] ?? false,
$config['http_only'] ?? true, false, $config['same_site'] ?? null
));
}
}
关于 Laravel Session 一点见解:
Session(会话)
Laravel 实现会话方式我举个形象例子来说明。我们去一家非常喜爱的饭店吃饭时,饭店推出了会员折扣活动,需要办理会员卡。办理者会获得饭店颁发的一张含有唯一标识符会员磁卡,磁卡仅仅记录一串标识符,真正会员数据存在饭店的数据库中。那么下一次会员来就餐时,凭借会员磁卡上的标识符,来获取会员的信息,其中包括就餐记录、剩余余额等等。
这段例子,其中会员就是浏览器;饭店就是 Laravel;会员磁卡就是 Laravel 的 Cookie,会员磁卡标识符就是 SessionId,真正的数据存在 Laravel 中。
那么真正数据在哪?
如图,sessions 目录中的一串串不认识的字符串文件,对应着一个个会员数据。其中字符串文件名就是会员磁卡的标识符也就是 sessionId啦。文件中的内容记录着经过 php 序列化后的用户数据。