Session與響應Session 這個東西還需要多說?學 PHP 或者任何 Web 開發(fā)語言的入門課好不好!既然這么說,那么看來你是沒用過 Laravel 自帶的 Session 呀,Laravel 的 Session 可不是用得 PHP 默認的那個 Session 哦。今天我們就來一起看看 Laravel 自己實現(xiàn)的這個 Session 是啥樣的。另外,請求流程我們在最早的時候就已經(jīng)講過了,但是響應一直都沒怎么提過,咱們也一起來看一看。 Session默認情況下對于普通的 Session 來說,我們的 PHP 會生成一個臨時文件存儲到系統(tǒng)的 /tmp 目錄下,里面的內(nèi)容實際上是一個序列化的對象結(jié)構(gòu)。這個大家可以自己去試試,如果需要改成使用 memcached 或者 redis 的話,需要修改 php.ini 文件,還是比較麻煩的。Laravel 框架沒有使用默認的 PHP 的 Session 來進行存儲,而是自己實現(xiàn)了一套。其實功能差不多,默認同樣是存儲成文件,但如果需要修改存儲方式的話,框架就會方便很多,畢竟不用去動 php.ini 文件。
在這里我們展示了三種操作 Session 的方式,一個是通過門面的 Session 對象,另一個則是通過請求對象 Request 的 session() 方法,最后還可以通過 session() 這個全局輔助函數(shù)來操作。put() 添加數(shù)據(jù),get() 獲取數(shù)據(jù),getId() 獲得 Session ID ,都非常簡單。 這個時候,你可以去 storage/framework/sessions 目錄下面找到對應 Session ID 名稱的緩存文件,它的內(nèi)容是下面這個樣子的。
后面的 _flash 是我們馬上要測試的另外一個功能保存的數(shù)據(jù),暫時可以忽略掉。是不是和 PHP 默認的 Session 沒啥區(qū)別,都是保存了一個序列化對象。另外需要注意的是,和普通的 Session 一樣,我們在使用普通的 Session 前需要 session_start() 一下,在 Laravel 中,則是需要保證 app/Http/Kernel.php 中 StartSession 中間件沒有被注釋掉,當然,默認它是打開的。 閃存閃存是什么東西?這可不是我們的 U盤 呀,而是一種一次性的 Session 機制。它的作用就是一個一次性的 Session 數(shù)據(jù),當數(shù)據(jù)被放到緩存后,下一次或者別的請求只能從 Session 中取一次這個數(shù)據(jù),再次取數(shù)據(jù)就沒有了。注意,一定是別的請求,而不是當前這個請求。
使用 flash() 方法就可以保存一條閃存數(shù)據(jù),它在數(shù)據(jù)中的格式就是我們上面看到的 Session 內(nèi)容中的 _flash 字段所保存的內(nèi)容,可以看到在這個對象中還包含了 _previous 字段,其中就有我們保存數(shù)據(jù)時的鏈接地址。 在保存數(shù)據(jù)時的地址訪問時,可以一直訪問,但當你使用別的鏈接訪問數(shù)據(jù)時,就只能訪問一次了,不信你可以試試多訪問幾次 session/test2 這條鏈接。這時,再查看保存的 Session 數(shù)據(jù),我們會發(fā)現(xiàn) b 的數(shù)據(jù)內(nèi)容已經(jīng)沒有了。
這個就是閃存的作用。如果你想要將這個數(shù)據(jù)保存到其它的請求,而不是在 test2 中使用的話,可以使用 reflash() 。或者你想要將這個數(shù)據(jù)轉(zhuǎn)換成正常的 Session 數(shù)據(jù),那么可以使用 keep() 方法。這兩個方法大家可以自己測試一下,官方文檔上都有代碼演示。 切換成 redis默認情況下,我們的 Session 走的是文件存儲,這個上面我們已經(jīng)看到了,而且也很方便地能夠找到生成的 Session 文件。對于正式的開發(fā)環(huán)境來說,稍微上一點規(guī)模的項目多少都會需要進行多臺服務器的分布式布局,這個時候,如果 Session 還是以文件形式分布在不同的服務器,就會出現(xiàn)很尷尬的局面,那就是用戶的請求可能并不一定每次都會落在同一臺服務器。于是,使用外部的 公共硬盤 或者使用 Redis 或者 Memcached 之類的緩存框架來進行 Session 的保存就是非常常見的做法了。相對于 公共硬盤 來說,肯定是緩存服務效率更好,而且也更便于維護。 Laravel 中使用 Redis 或 Memcached 來進行 Session 保存非常簡單,只需要修改 .env 配置文件就可以了,這里我們就以 Redis 為例。
直接修改 SESSION_DRIVER 驅(qū)動為 redis 即可,下面的 SESSION_CONNECTION 則是指定要使用的連接,也就是我們在 config/db.php 中配置的連接。 通過設置之后,我們再次訪問測試頁面,然后直接在 redis 中就可以看到一個 laravel_database_laravel_cache:SZL5LXKfJTm9ZRotUZRxM59qXO4IcmdKollBMFW9 鍵的緩存數(shù)據(jù),里面的內(nèi)容就是我們的 Session 信息。這個 key 使用的依然是 Laravel 生成的那個 Session ID 。 阻塞默認情況下,Laravel 是允許使用同一 Session 的請求并發(fā)執(zhí)行的。但是一小部分應用程序中可能會丟失 Session ,比如兩個請求同時到達,其中一個設置另一個讀取,這時候,讀取的請求可能就是無法讀取到內(nèi)容的,或者兩個請求同時寫入同一個 Session 。其實這就是一個并發(fā)的問題,一般情況下,我們在 Swoole 或者 Java 中會加鎖來實現(xiàn),而 Laravel 框架則是提供了一個阻塞的能力。
在這段代碼中,我們設置了一個閃存數(shù)據(jù),同一個請求中,閃存可以無限次訪問。然后我們讓代碼停頓 10秒 用于測試。接下來就是使用了一個 block() 方法來進行阻塞,它有兩個參數(shù),一個是 lockSeconds 表示加鎖時間,另一個 waitSeconds 表示等待時間。 加鎖時間也就是阻塞時間,如果請求的執(zhí)行時間長,則在阻塞時間內(nèi)會鎖住請求,另一個請求的等待時間則是有鎖情況下會一直等待會話鎖的完成,如果超過了設置的 10秒 則會返回 LockTimeoutException 異常。大家可以先運行行一個請求,在等待的時候再運行第二個請求,當 sleep() 結(jié)束后,兩個請求的結(jié)果才會返回。 Session 實現(xiàn)相信大家對于如何找到源碼實現(xiàn)內(nèi)容已經(jīng)非常熟悉了,那么我也就不多說了,直接去找到 vendor/laravel/framework/src/Illuminate/Session/SessionManager.php 就可以了。在這個類中,我們可以看到許多的 Session 驅(qū)動,依然還是以 Redis 的來看一看。
在 bulidSession 中,我們獲得的是一個 Store 對象,傳遞進去的 handler 是在上面的 createRedisDriver() 中通過 createCacheHandler() 方法中定義的,其實在這個方法中,就是通過 服務容器 獲得了一個 Cache 對象。當你使用 SESSION 門面或者 session() 輔助函數(shù)調(diào)用 Session 的操作函數(shù)時,其實是在 SessionManager 繼承的 Manager 對象中,它實現(xiàn)了 __call 方法的調(diào)用,實際上最后調(diào)用的都是 Store 對象。
接下來,在 vendor/laravel/framework/src/Illuminate/Session/Store.php 類中,我們就可以看到各種 Session 方法,在這里比較有意思的是,它是以這個對象進行保存的,也就是說,在執(zhí)行 put()、get() 之類的方法時,其實操作的是 Store 中的數(shù)組
那么 Session 是在什么時候保存的呢?這個就要看 startSession 這個中間件了。
在 startSession 中間件中,handle() 最后會調(diào)用 handleStatefulRequest() 這個方法,可以看出,這個方法是一個后置中間件,在請求操作結(jié)束后,調(diào)用了 saveSession() 方法,它實際上調(diào)用的是 Manager 中的 save() 方法。關于這一塊,大家可以自己嘗試一下,讓一個請求暫停然后看 Session 文件里數(shù)據(jù)有沒有變化,然后暫停完成之后再看一下就明白了。當然,我們也可以手動調(diào)用 save() 方法實時保存。 響應對于請求流程,大家已經(jīng)非常熟悉了,也了解過在控制器或者路由中,想要返回響應的內(nèi)容,直接 retrun 就可以了,不過對于具體的響應操作我們還是沒有進行過深入的學習。今天就一起來學習一下響應的具體內(nèi)容。 添加響應頭及 Cookie如果要返回響應內(nèi)容,直接 return 數(shù)據(jù)就可以了,但如果想為響應增加頭和 Cookie 信息的話,最簡單的就是使用 response() 輔助函數(shù)。
header() 方法可以指定單個響應頭,而 withHeaders() 則可以以數(shù)組的方式設置多個響應頭。cookie() 方法則是設置 Cookie 的方法,它的參數(shù)和我們普通的 Cookie 操作函數(shù)的參數(shù)是一致的,后面的可選參數(shù)中也可以設置過期時間、HttpOnly 等內(nèi)容。 重定向與文件下載對于重定向來說,我們可以看成是跳轉(zhuǎn)至某個頁面,可以直接寫路由、使用路由別名,也可以直接跳轉(zhuǎn)到某個控制器方法。
上面的測試代碼中,第二行注釋起來的測試代碼我們還可以指定重定向的狀態(tài)碼。默認情況下走的是 302 跳轉(zhuǎn),在這里我們可以設置成 301 跳轉(zhuǎn)。關于 302 和 301 的區(qū)別我就不再多說了,一個是臨時重定向,一個是永久重定向,如果有不明白的小伙伴可以去查詢一下相關的資料。不過更推薦的是好好學習一下 HTTP 相關的知識。
文件下載有一個非常簡單的函數(shù)就是直接使用 download() 函數(shù),里面指定文件路徑就可以了。同時還有別的方式可以實現(xiàn)文件的下載,文檔中寫得很詳細了,這里就不多說了。 響應流程對于響應來說,通過查閱 response() 方法的實現(xiàn)就可以發(fā)現(xiàn)它返回的是一個 vendor/laravel/framework/src/Illuminate/Http/Response.php 對象,而這個對象又是繼承自 Symfony 的 vendor/symfony/http-foundation/Response.php 。就和請求一樣,它的底層實現(xiàn)依然是 Symfony 框架中的響應實現(xiàn)。 首先到 public/index.php 入口文件中,我們會發(fā)現(xiàn)這樣的一段代碼。
在這里,Kernel 的 Handle() 方法實際上返回的就是一個 Response 對象。
sendRequestThroughRouter() 方法在之前的 中間件 以及 服務容器 和 管道 相關的文章中都接觸過,他就是我們請求處理的核心流程,在請求的最后,就會返回響應結(jié)果。在 index.php 中,Kernel 執(zhí)行完 handle() 之后,會再調(diào)用一個 send() 方法。這個方法存在于 vendor/symfony/http-foundation/Response.php 中。
接下來我們再進入到 sendHeaders() 和 sendContent() 中。
嗯,還需要繼續(xù)解釋嗎?相信大家已經(jīng)明白最后的輸出就在這里完成了吧! 總結(jié)今天學習了兩塊內(nèi)容,不過其實都和請求響應有關,Session 是非常常用的功能,響應也是所有請求必不可少的。Session 之所以框架要重寫一套而不用原生的,也是為了靈活起見,我們不需要去 php.ini 配置文件修改 Session 相關的功能。而響應則走的依然是 Symfony 的底層框架功能。就像 Laravel 的口號一樣,讓實現(xiàn)的代碼更優(yōu)雅,從而對這些功能又重新進行更適合自己的封裝。 參考文檔: https:///docs/laravel/8.x/session/9373 https:///docs/laravel/8.x/responses/9370 |
|
來自: 硬核項目經(jīng)理 > 《待分類》