您好,登錄后才能下訂單哦!
Laravel 默認 Session 中間件導致的 Session 寫入失效問題詳解,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
最近由于項目開發需要,手機客戶端和網頁端統一使用一套接口,為保證 會話(Session) 能夠正常且在各類情況下兼容,我希望能夠改變 SessionID 的獲取方式。默認情況下,所有網站都是通過 HTTP 請求的 Header 頭部中的 Cookie 實現的,通過 Cookie 中指定的 SessionID 來關聯到服務端對應數據,從而實現會話功能。
但對于手機客戶端,可能并不會支持原始的 Cookie,亦或者根據平臺需要而屏蔽,因此開發中要求通過增加一個請求頭 X-Session-Token 來標識 SessionID。在 Laravel 框架中,實現 Session 初始化、讀取和啟動,都是通過 Illuminate\Session\Middleware\StartSession 這個中間件實現的,該中間件有一個關鍵方法 getSession ,這個方法就是獲取 SessionId 從而告知 Session 組件以什么憑據恢復 Session 數據。
該中間件注冊于 app/Http/Kernel.php 文件下。
我新建了一個類繼承該中間件,同時替換了在 app/Http/Kernel.php 下的注冊的地方,原來的 getSession 方法源碼如下:
public function getSession(Request $request) { $session = $this->manager->driver(); $session->setId($request->cookies->get($session->getName())); return $session; }
在新的中間件中,我修改為:
public function getSession(Request $request) { $session = $this->manager->driver(); // 判斷是否是接口訪問并根據實際情況選擇 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; }
但是麻煩也隨之而來。。。
修改完后,推送至分支,在合并至主開發分支之前往往需要跑一下單元測試,不幸的是,之前通過的 Case 這回竟然報錯,問題是 CSRF 組件 報出 Token 錯誤,而我們在這一處提供的 Token 跟平時并無二致,問題肯定出在 Session 上。
值得注意的是,我修改中間件的代碼,對框架的影響可以說根本沒有,事實上也確實沒有,因為我將我自己創建的中間件代碼修改成繼承的中間件代碼一致也無濟于事,但奇怪的是,在我將中間件換回原來的中間件就沒有這個問題。
于是我將正常情況下和非正常情況下的代碼都跑了一遍,在關鍵處斷點調試,發現問題出在中間件的一個重要屬性 $sessionHandled , 若該值為 false 則會引起我們之前的狀況。關鍵在于,中間件啟動之時,都會走 handle 方法,而對于 Session 這個中間件, handle 方法的第一行代碼就是:
$this->sessionHandled = true;
Interesting。。。
我們知道。Laravel 框架的特色是其 IoC 容器,框架中初始化各種類都是由其負責以實現各種依賴注入,以保證組件間的松耦合。中間件定然不例外。要知道,單例和普通實例最大的區別在于無論創建多少次,單例永遠都是一個,實例中的屬性不會被初始化,因此無問題的中間件必然是一個單例,而我自己創建的中間件只是個普通的類的實例。但本著知其然更要知其所以然,我需要確認我這一想法(其實解決辦法已經想到了,后面說)。
那么問題大致就在于初始化中間件這塊了,于是不得不打起精神,仔細理一下 Laravel 的啟動代碼。而這里面的重點,在于一個叫 Illuminate\Pipeline\Pipeline 的類。
這個類有三個重要方法 send 、 through 、 then 。其中 then 是開始一切的鑰匙。這個類主要是連續執行幾個框架啟動步驟的玩意兒,首先是初始化處理過程需要的組件(Request 和 中間件),其次是將請求通過這些處理組件構成的堆棧(一堆中間件和路由派發組件),最后是返回處理結果(Response)。
可以說這玩意兒是 Laravel Http 部分的核心(額,,本來就是 Kernel)。那么之前的問題就在于 Pipeline 的 then 方法和其調用的 getSlice 方法,直接觀察 getSlice 方法,可以發現它負責的是生成處理堆棧,并實例化 Middleware (中間件)類,整個方法代碼如下:
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)); } }; }; }
可以注意到 $this->container->make($name) ,這意味著其初始化一個中間件類,單純的就是 make,若其不是單例則反復 new ,導致之前的屬性被初始化。
那么解決辦法也顯而易見面,使其成為一個單例。
我在 app/Providers/AppServiceProvider.php 的 register 方法中添加如下一行代碼,就解決了之前的問題:
$this->app->singleton(SessionStart::class); // SessionStart 是我那個中間件類名
看完上述內容,你們掌握Laravel 默認 Session 中間件導致的 Session 寫入失效問題詳解的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。