《PHP實戰(zhàn):關(guān)于擴(kuò)展 Laravel 默認(rèn) Session 中間件導(dǎo)致的 Session 寫入失效問題分析》要點:
本文介紹了PHP實戰(zhàn):關(guān)于擴(kuò)展 Laravel 默認(rèn) Session 中間件導(dǎo)致的 Session 寫入失效問題分析,希望對您有用。如果有疑問,可以聯(lián)系我們。
PHP實戰(zhàn)最近由于項目開發(fā)需要,手機(jī)客戶端和網(wǎng)頁端統(tǒng)一使用一套接口,為保證 會話(Session) 能夠正常且在各類情況下兼容,我希望能夠改變 SessionID 的獲取方式.默認(rèn)情況下,所有網(wǎng)站都是通過 HTTP 哀求的 Header 頭部中的 Cookie 實現(xiàn)的,通過 Cookie 中指定的 SessionID 來關(guān)聯(lián)到服務(wù)端對應(yīng)數(shù)據(jù),從而實現(xiàn)會話功能.
PHP實戰(zhàn)但對于手機(jī)客戶端,可能并不會支持原始的 Cookie,亦或者根據(jù)平臺需要而屏蔽,因此開發(fā)中要求通過增加一個哀求頭 X-Session-Token 來標(biāo)識 SessionID.在 Laravel 框架中,實現(xiàn) Session 初始化、讀取和啟動,都是通過 Illuminate\Session\Middleware\StartSession 這個中間件實現(xiàn)的,該中間件有一個關(guān)鍵方法 getSession ,這個方法就是獲取 SessionId 從而告知 Session 組件以什么憑據(jù)恢復(fù) Session 數(shù)據(jù).
PHP實戰(zhàn)該中間件注冊于 app/Http/Kernel.php 文件下.
PHP實戰(zhàn)我新建了一個類繼承該中間件,同時替換了在 app/Http/Kernel.php 下的注冊的地方,原來的 getSession 辦法源碼如下:
PHP實戰(zhàn)
public function getSession(Request $request)
{
$session = $this->manager->driver();
$session->setId($request->cookies->get($session->getName()));
return $session;
}
PHP實戰(zhàn)在新的中間件中,我修改為:
PHP實戰(zhàn)
public function getSession(Request $request)
{
$session = $this->manager->driver();
// 判斷是否是接口拜訪并根據(jù)實際情況選擇 SessionID 的獲取方式
if ($request->headers->has('x-session-token')) {
$sessionId = $request->headers->has('x-session-token');
} else {
$sessionId = $request->cookies->get($session->getName());
}
$session->setId($sessionId);
return $session;
}
PHP實戰(zhàn)但是麻煩也隨之而來...
PHP實戰(zhàn)修改完后,推送至分支,在合并至主開發(fā)分支之前往往必要跑一下單元測試,不幸的是,之前通過的 Case 這回竟然報錯,問題是 CSRF 組件 報出 Token 錯誤,而我們在這一處提供的 Token 跟平時并無二致,問題肯定出在 Session 上.
PHP實戰(zhàn)值得注意的是,我修改中間件的代碼,對框架的影響可以說根本沒有,事實上也確實沒有,因為我將我自己創(chuàng)建的中間件代碼修改成繼承的中間件代碼一致也無濟(jì)于事,但奇怪的是,在我將中間件換回本來的中間件就沒有這個問題.
PHP實戰(zhàn)于是我將正常情況下和非正常情況下的代碼都跑了一遍,在關(guān)鍵處斷點調(diào)試,發(fā)現(xiàn)問題出在中間件的一個重要屬性 $sessionHandled , 若該值為 false 則會引起我們之前的狀況.關(guān)鍵在于,中間件啟動之時,都會走 handle 辦法,而對于 Session 這個中間件, handle 辦法的第一行代碼就是:
PHP實戰(zhàn)$this->sessionHandled = true;
PHP實戰(zhàn)Interesting...
PHP實戰(zhàn)我們知道.Laravel 框架的特色是其 IoC 容器,框架中初始化各種類都是由其負(fù)責(zé)以實現(xiàn)各種依賴注入,以保證組件間的松耦合.中間件定然不例外.要知道,單例和普通實例最大的區(qū)別在于無論創(chuàng)建多少次,單例永遠(yuǎn)都是一個,實例中的屬性不會被初始化,因此無問題的中間件必然是一個單例,而我自己創(chuàng)建的中間件只是個普通的類的實例.但本著知其然更要知其所以然,我需要確認(rèn)我這一想法(其實解決方法已經(jīng)想到了,后面說).
PHP實戰(zhàn)那么問題大致就在于初始化中間件這塊了,于是不得不打起精神,仔細(xì)理一下 Laravel 的啟動代碼.而這里面的重點,在于一個叫 Illuminate\Pipeline\Pipeline 的類.
PHP實戰(zhàn)這個類有三個重要方法 send 、 through 、 then .其中 then 是開始一切的鑰匙.這個類主要是連續(xù)執(zhí)行幾個框架啟動步驟的玩意兒,首先是初始化處理過程需要的組件(Request 和 中間件),其次是將哀求通過這些處理組件構(gòu)成的堆棧(一堆中間件和路由派發(fā)組件),最后是返回處理結(jié)果(Response).
PHP實戰(zhàn)可以說這玩意兒是 Laravel Http 部分的核心(額,,本來就是 Kernel).那么之前的問題就在于 Pipeline 的 then 辦法和其調(diào)用的 getSlice 辦法,直接觀察 getSlice 辦法,可以發(fā)現(xiàn)它負(fù)責(zé)的是生成處理堆棧,并實例化 Middleware (中間件)類,整個辦法代碼如下:
PHP實戰(zhàn)
protected function getSlice()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if ($pipe instanceof Closure) {
return call_user_func($pipe, $passable, $stack);
} else {
list($name, $parameters) = $this->parsePipeString($pipe);
return call_user_func_array([$this->container->make($name), $this->method],
array_merge([$passable, $stack], $parameters));
}
};
};
}
PHP實戰(zhàn)可以注意到 $this->container->make($name) ,這意味著其初始化一個中間件類,單純的便是 make,若其不是單例則反復(fù) new ,導(dǎo)致之前的屬性被初始化.
PHP實戰(zhàn)那么解決方法也顯而易見面,使其成為一個單例.
PHP實戰(zhàn)我在 app/Providers/AppServiceProvider.php 的 register 辦法中添加如下一行代碼,就解決了之前的問題:
PHP實戰(zhàn)$this->app->singleton(SessionStart::class); // SessionStart 是我那個中間件類名
PHP實戰(zhàn)以上給大家介紹了擴(kuò)展 Laravel 默認(rèn) Session 中間件導(dǎo)致的 Session 寫入失效問題闡發(fā)的全部內(nèi)容,希望大家喜歡.
維易PHP培訓(xùn)學(xué)院每天發(fā)布《PHP實戰(zhàn):關(guān)于擴(kuò)展 Laravel 默認(rèn) Session 中間件導(dǎo)致的 Session 寫入失效問題分析》等實戰(zhàn)技能,PHP、MYSQL、LINUX、APP、JS,CSS全面培養(yǎng)人才。
轉(zhuǎn)載請注明本頁網(wǎng)址:
http://www.fzlkiss.com/jiaocheng/7767.html