日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

【Laravel系列7.3】Session與響應

 硬核項目經(jīng)理 2022-06-02 發(fā)布于湖南

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 文件。

Route::get('session/test'function(){
    \Illuminate\Support\Facades\Session::put('a''aaaaaaa');
    echo request()->session()->get('a'); // aaaaaaa
    echo session()->get('a'); // aaaaaaa

    echo \Illuminate\Support\Facades\Session::getId(); // SZL5LXKfJTm9ZRotUZRxM59qXO4IcmdKollBMFW9
});

在這里我們展示了三種操作 Session 的方式,一個是通過門面的 Session 對象,另一個則是通過請求對象 Request 的 session() 方法,最后還可以通過 session() 這個全局輔助函數(shù)來操作。put() 添加數(shù)據(jù),get() 獲取數(shù)據(jù),getId() 獲得 Session ID ,都非常簡單。

這個時候,你可以去 storage/framework/sessions 目錄下面找到對應 Session ID 名稱的緩存文件,它的內(nèi)容是下面這個樣子的。

a:5:{s:6:"_token";s:40:"EZOEx7QwZNKbibc9hHOqJRo07Srd5RT9q5mYVmVC";s:1:"a";s:7:"aaaaaaa";s:6:"_flash";a:2:{s:3:"new";a:0:{}s:3:"old";a:1:{i:0;s:1:"b";}}s:9:"_previous";a:1:{s:3:"url";s:28:"http://laravel8/session/test";}s:1:"b";s:3:"bbb";}

后面的 _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ù)就沒有了。注意,一定是別的請求,而不是當前這個請求。

Route::get('session/test'function(){
    \Illuminate\Support\Facades\Session::flash('b''bbb');

    echo request()->session()->get('b');
    echo request()->session()->get('b');
});
Route::get('session/test2'function(){
    echo request()->session()->get('b');
});

使用 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)沒有了。

a:4:{s:6:"_token";s:40:"5I8GP7LNzygNcYeTKer8kINqdxMGY1ArTqAXd13n";s:1:"a";s:7:"aaaaaaa";s:6:"_flash";a:2:{s:3:"new";a:0:{}s:3:"old";a:0:{}}s:9:"_previous";a:1:{s:3:"url";s:29:"http://laravel8/session/test2";}}

這個就是閃存的作用。如果你想要將這個數(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=redis
SESSION_CONNECTION=default

直接修改 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 框架則是提供了一個阻塞的能力。

Route::get('session/test'function(){
    \Illuminate\Support\Facades\Session::flash('b''bbb');

    echo request()->session()->get('b');
    echo request()->session()->get('b');
    sleep(10);
})->block($lockSeconds = 10, $waitSeconds = 10);

Route::get('session/test2'function(){
    echo request()->session()->get('b');
})->block($lockSeconds = 10, $waitSeconds = 10);

在這段代碼中,我們設置了一個閃存數(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 的來看一看。

protected function createRedisDriver()
{
    $handler = $this->createCacheHandler('redis');

    $handler->getCache()->getStore()->setConnection(
        $this->config->get('session.connection')
    );

    return $this->buildSession($handler);
}
protected function buildSession($handler)
{
    return $this->config->get('session.encrypt')
            ? $this->buildEncryptedSession($handler)
            : new Store($this->config->get('session.cookie'), $handler);
}
protected function createCacheHandler($driver)
{
    $store = $this->config->get('session.store') ?: $driver;

    return new CacheBasedSessionHandler(
        clone $this->container->make('cache')->store($store),
        $this->config->get('session.lifetime')
    );
}

在 bulidSession 中,我們獲得的是一個 Store 對象,傳遞進去的 handler 是在上面的 createRedisDriver() 中通過 createCacheHandler() 方法中定義的,其實在這個方法中,就是通過 服務容器 獲得了一個 Cache 對象。當你使用 SESSION 門面或者 session() 輔助函數(shù)調(diào)用 Session 的操作函數(shù)時,其實是在 SessionManager 繼承的 Manager 對象中,它實現(xiàn)了 __call 方法的調(diào)用,實際上最后調(diào)用的都是 Store 對象。

public function __call($method, $parameters)
{
    return $this->driver()->$method(...$parameters);
}

接下來,在 vendor/laravel/framework/src/Illuminate/Session/Store.php 類中,我們就可以看到各種 Session 方法,在這里比較有意思的是,它是以這個對象進行保存的,也就是說,在執(zhí)行 put()、get() 之類的方法時,其實操作的是 Store 中的數(shù)組

public function get($key, $default = null)
{
    return Arr::get($this->attributes, $key, $default);
}
public function put($key, $value = null)
{
    if (! is_array($key)) {
        $key = [$key => $value];
    }

    foreach ($key as $arrayKey => $arrayValue) {
        Arr::set($this->attributes, $arrayKey, $arrayValue);
    }
}

那么 Session 是在什么時候保存的呢?這個就要看 startSession 這個中間件了。

// vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php
protected function handleStatefulRequest(Request $request, $session, Closure $next)
{
    // 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.
    $request->setLaravelSession(
        $this->startSession($request, $session)
    );

    $this->collectGarbage($session);

    $response = $next($request);

    $this->storeCurrentUrl($request, $session);

    $this->addCookieToResponse($response, $session);

    // 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.
    $this->saveSession($request);

    return $response;
}

在 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ù)。

Route::get('response/test1'function(){
    return response('Hello test1'200)
        ->header('Content-type''application/json')
        ->withHeaders([
            'A'=>'A info',
            'B'=>'B info'
        ])
        ->cookie('oppo''o', );
});

header() 方法可以指定單個響應頭,而 withHeaders() 則可以以數(shù)組的方式設置多個響應頭。cookie() 方法則是設置 Cookie 的方法,它的參數(shù)和我們普通的 Cookie 操作函數(shù)的參數(shù)是一致的,后面的可選參數(shù)中也可以設置過期時間、HttpOnly 等內(nèi)容。

重定向與文件下載

對于重定向來說,我們可以看成是跳轉(zhuǎn)至某個頁面,可以直接寫路由、使用路由別名,也可以直接跳轉(zhuǎn)到某個控制器方法。

Route::get('response/test2'function(){
//    return redirect('response/test1');
//    return redirect('response/test1',301);
//    return redirect()->route('rt3');
    return redirect()->action([\App\Http\Controllers\TestController::class, 'test2'], ['id'=>1]);

});

Route::name('rt3')->get('response/test3'function(){
    echo 111;
});

上面的測試代碼中,第二行注釋起來的測試代碼我們還可以指定重定向的狀態(tài)碼。默認情況下走的是 302 跳轉(zhuǎn),在這里我們可以設置成 301 跳轉(zhuǎn)。關于 302 和 301 的區(qū)別我就不再多說了,一個是臨時重定向,一個是永久重定向,如果有不明白的小伙伴可以去查詢一下相關的資料。不過更推薦的是好好學習一下 HTTP 相關的知識。

Route::get('response/test4'function(){
    return response()->download(\Illuminate\Support\Facades\Storage::path('public/8cb3c505713a1e861169aa227ee1c37c.jpg'));
});

文件下載有一個非常簡單的函數(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)這樣的一段代碼。

$response = tap($kernel->handle(
    $request = Request::capture()
))->send();

在這里,Kernel 的 Handle() 方法實際上返回的就是一個 Response 對象。

public function handle($request)
{
    try {
        $request->enableHttpMethodParameterOverride();

        $response = $this->sendRequestThroughRouter($request);
    } catch (Throwable $e) {
        $this->reportException($e);

        $response = $this->renderException($request, $e);
    }

    $this->app['events']->dispatch(
        new RequestHandled($request, $response)
    );

    return $response;
}

sendRequestThroughRouter() 方法在之前的 中間件 以及 服務容器 和 管道 相關的文章中都接觸過,他就是我們請求處理的核心流程,在請求的最后,就會返回響應結(jié)果。在 index.php 中,Kernel 執(zhí)行完 handle() 之后,會再調(diào)用一個 send() 方法。這個方法存在于 vendor/symfony/http-foundation/Response.php 中。

public function send()
{
    $this->sendHeaders();
    $this->sendContent();

    if (\function_exists('fastcgi_finish_request')) {
        fastcgi_finish_request();
    } elseif (!\in_array(\PHP_SAPI, ['cli''phpdbg'], true)) {
        static::closeOutputBuffers(0true);
    }

    return $this;
}

接下來我們再進入到 sendHeaders() 和 sendContent() 中。

public function sendHeaders()
{
    // headers have already been sent by the developer
    if (headers_sent()) {
        return $this;
    }

    // headers
    foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
        $replace = 0 === strcasecmp($name, 'Content-Type');
        foreach ($values as $value) {
            header($name.': '.$value, $replace, $this->statusCode);
        }
    }

    // cookies
    foreach ($this->headers->getCookies() as $cookie) {
        header('Set-Cookie: '.$cookie, false$this->statusCode);
    }

    // status
    header(sprintf('HTTP/%s %s %s'$this->version, $this->statusCode, $this->statusText), true$this->statusCode);

    return $this;
}

public function sendContent()
{
    echo $this->content;

    return $this;
}

嗯,還需要繼續(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

    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多