乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      【Laravel系列7.3】Session與響應(yīng)

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

      Session與響應(yīng)

      Session 這個(gè)東西還需要多說(shuō)?學(xué) PHP 或者任何 Web 開(kāi)發(fā)語(yǔ)言的入門課好不好!既然這么說(shuō),那么看來(lái)你是沒(méi)用過(guò) Laravel 自帶的 Session 呀,Laravel 的 Session 可不是用得 PHP 默認(rèn)的那個(gè) Session 哦。今天我們就來(lái)一起看看 Laravel 自己實(shí)現(xiàn)的這個(gè) Session 是啥樣的。另外,請(qǐng)求流程我們?cè)谧钤绲臅r(shí)候就已經(jīng)講過(guò)了,但是響應(yīng)一直都沒(méi)怎么提過(guò),咱們也一起來(lái)看一看。

      Session

      默認(rèn)情況下對(duì)于普通的 Session 來(lái)說(shuō),我們的 PHP 會(huì)生成一個(gè)臨時(shí)文件存儲(chǔ)到系統(tǒng)的 /tmp 目錄下,里面的內(nèi)容實(shí)際上是一個(gè)序列化的對(duì)象結(jié)構(gòu)。這個(gè)大家可以自己去試試,如果需要改成使用 memcached 或者 redis 的話,需要修改 php.ini 文件,還是比較麻煩的。Laravel 框架沒(méi)有使用默認(rèn)的 PHP 的 Session 來(lái)進(jìn)行存儲(chǔ),而是自己實(shí)現(xiàn)了一套。其實(shí)功能差不多,默認(rèn)同樣是存儲(chǔ)成文件,但如果需要修改存儲(chǔ)方式的話,框架就會(huì)方便很多,畢竟不用去動(dòng) 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 的方式,一個(gè)是通過(guò)門面的 Session 對(duì)象,另一個(gè)則是通過(guò)請(qǐng)求對(duì)象 Request 的 session() 方法,最后還可以通過(guò) session() 這個(gè)全局輔助函數(shù)來(lái)操作。put() 添加數(shù)據(jù),get() 獲取數(shù)據(jù),getId() 獲得 Session ID ,都非常簡(jiǎn)單。

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

      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 是我們馬上要測(cè)試的另外一個(gè)功能保存的數(shù)據(jù),暫時(shí)可以忽略掉。是不是和 PHP 默認(rèn)的 Session 沒(méi)啥區(qū)別,都是保存了一個(gè)序列化對(duì)象。另外需要注意的是,和普通的 Session 一樣,我們?cè)谑褂闷胀ǖ?Session 前需要 session_start() 一下,在 Laravel 中,則是需要保證 app/Http/Kernel.php 中 StartSession 中間件沒(méi)有被注釋掉,當(dāng)然,默認(rèn)它是打開(kāi)的。

      閃存

      閃存是什么東西?這可不是我們的 U盤 呀,而是一種一次性的 Session 機(jī)制。它的作用就是一個(gè)一次性的 Session 數(shù)據(jù),當(dāng)數(shù)據(jù)被放到緩存后,下一次或者別的請(qǐng)求只能從 Session 中取一次這個(gè)數(shù)據(jù),再次取數(shù)據(jù)就沒(méi)有了。注意,一定是別的請(qǐng)求,而不是當(dāng)前這個(gè)請(qǐng)求。

      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)容,可以看到在這個(gè)對(duì)象中還包含了 _previous 字段,其中就有我們保存數(shù)據(jù)時(shí)的鏈接地址。

      在保存數(shù)據(jù)時(shí)的地址訪問(wèn)時(shí),可以一直訪問(wèn),但當(dāng)你使用別的鏈接訪問(wèn)數(shù)據(jù)時(shí),就只能訪問(wèn)一次了,不信你可以試試多訪問(wèn)幾次 session/test2 這條鏈接。這時(shí),再查看保存的 Session 數(shù)據(jù),我們會(huì)發(fā)現(xiàn) b 的數(shù)據(jù)內(nèi)容已經(jīng)沒(méi)有了。

      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";}}

      這個(gè)就是閃存的作用。如果你想要將這個(gè)數(shù)據(jù)保存到其它的請(qǐng)求,而不是在 test2 中使用的話,可以使用 reflash() ?;蛘吣阆胍獙⑦@個(gè)數(shù)據(jù)轉(zhuǎn)換成正常的 Session 數(shù)據(jù),那么可以使用 keep() 方法。這兩個(gè)方法大家可以自己測(cè)試一下,官方文檔上都有代碼演示。

      切換成 redis

      默認(rèn)情況下,我們的 Session 走的是文件存儲(chǔ),這個(gè)上面我們已經(jīng)看到了,而且也很方便地能夠找到生成的 Session 文件。對(duì)于正式的開(kāi)發(fā)環(huán)境來(lái)說(shuō),稍微上一點(diǎn)規(guī)模的項(xiàng)目多少都會(huì)需要進(jìn)行多臺(tái)服務(wù)器的分布式布局,這個(gè)時(shí)候,如果 Session 還是以文件形式分布在不同的服務(wù)器,就會(huì)出現(xiàn)很尷尬的局面,那就是用戶的請(qǐng)求可能并不一定每次都會(huì)落在同一臺(tái)服務(wù)器。于是,使用外部的 公共硬盤 或者使用 Redis 或者 Memcached 之類的緩存框架來(lái)進(jìn)行 Session 的保存就是非常常見(jiàn)的做法了。相對(duì)于 公共硬盤 來(lái)說(shuō),肯定是緩存服務(wù)效率更好,而且也更便于維護(hù)。

      Laravel 中使用 Redis 或 Memcached 來(lái)進(jìn)行 Session 保存非常簡(jiǎn)單,只需要修改 .env 配置文件就可以了,這里我們就以 Redis 為例。

      SESSION_DRIVER=redis
      SESSION_CONNECTION=default

      直接修改 SESSION_DRIVER 驅(qū)動(dòng)為 redis 即可,下面的 SESSION_CONNECTION 則是指定要使用的連接,也就是我們?cè)?config/db.php 中配置的連接。

      通過(guò)設(shè)置之后,我們?cè)俅卧L問(wèn)測(cè)試頁(yè)面,然后直接在 redis 中就可以看到一個(gè) laravel_database_laravel_cache:SZL5LXKfJTm9ZRotUZRxM59qXO4IcmdKollBMFW9 鍵的緩存數(shù)據(jù),里面的內(nèi)容就是我們的 Session 信息。這個(gè) key 使用的依然是 Laravel 生成的那個(gè) Session ID 。

      阻塞

      默認(rèn)情況下,Laravel 是允許使用同一 Session 的請(qǐng)求并發(fā)執(zhí)行的。但是一小部分應(yīng)用程序中可能會(huì)丟失 Session ,比如兩個(gè)請(qǐng)求同時(shí)到達(dá),其中一個(gè)設(shè)置另一個(gè)讀取,這時(shí)候,讀取的請(qǐng)求可能就是無(wú)法讀取到內(nèi)容的,或者兩個(gè)請(qǐng)求同時(shí)寫(xiě)入同一個(gè) Session 。其實(shí)這就是一個(gè)并發(fā)的問(wèn)題,一般情況下,我們?cè)?Swoole 或者 Java 中會(huì)加鎖來(lái)實(shí)現(xiàn),而 Laravel 框架則是提供了一個(gè)阻塞的能力。

      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);

      在這段代碼中,我們?cè)O(shè)置了一個(gè)閃存數(shù)據(jù),同一個(gè)請(qǐng)求中,閃存可以無(wú)限次訪問(wèn)。然后我們讓代碼停頓 10秒 用于測(cè)試。接下來(lái)就是使用了一個(gè) block() 方法來(lái)進(jìn)行阻塞,它有兩個(gè)參數(shù),一個(gè)是 lockSeconds 表示加鎖時(shí)間,另一個(gè) waitSeconds 表示等待時(shí)間。

      加鎖時(shí)間也就是阻塞時(shí)間,如果請(qǐng)求的執(zhí)行時(shí)間長(zhǎng),則在阻塞時(shí)間內(nèi)會(huì)鎖住請(qǐng)求,另一個(gè)請(qǐng)求的等待時(shí)間則是有鎖情況下會(huì)一直等待會(huì)話鎖的完成,如果超過(guò)了設(shè)置的 10秒 則會(huì)返回 LockTimeoutException 異常。大家可以先運(yùn)行行一個(gè)請(qǐng)求,在等待的時(shí)候再運(yùn)行第二個(gè)請(qǐng)求,當(dāng) sleep() 結(jié)束后,兩個(gè)請(qǐng)求的結(jié)果才會(huì)返回。

      Session 實(shí)現(xiàn)

      相信大家對(duì)于如何找到源碼實(shí)現(xiàn)內(nèi)容已經(jīng)非常熟悉了,那么我也就不多說(shuō)了,直接去找到 vendor/laravel/framework/src/Illuminate/Session/SessionManager.php 就可以了。在這個(gè)類中,我們可以看到許多的 Session 驅(qū)動(dòng),依然還是以 Redis 的來(lái)看一看。

      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 中,我們獲得的是一個(gè) Store 對(duì)象,傳遞進(jìn)去的 handler 是在上面的 createRedisDriver() 中通過(guò) createCacheHandler() 方法中定義的,其實(shí)在這個(gè)方法中,就是通過(guò) 服務(wù)容器 獲得了一個(gè) Cache 對(duì)象。當(dāng)你使用 SESSION 門面或者 session() 輔助函數(shù)調(diào)用 Session 的操作函數(shù)時(shí),其實(shí)是在 SessionManager 繼承的 Manager 對(duì)象中,它實(shí)現(xiàn)了 __call 方法的調(diào)用,實(shí)際上最后調(diào)用的都是 Store 對(duì)象。

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

      接下來(lái),在 vendor/laravel/framework/src/Illuminate/Session/Store.php 類中,我們就可以看到各種 Session 方法,在這里比較有意思的是,它是以這個(gè)對(duì)象進(jìn)行保存的,也就是說(shuō),在執(zhí)行 put()、get() 之類的方法時(shí),其實(shí)操作的是 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 是在什么時(shí)候保存的呢?這個(gè)就要看 startSession 這個(gè)中間件了。

      // 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() 最后會(huì)調(diào)用 handleStatefulRequest() 這個(gè)方法,可以看出,這個(gè)方法是一個(gè)后置中間件,在請(qǐng)求操作結(jié)束后,調(diào)用了 saveSession() 方法,它實(shí)際上調(diào)用的是 Manager 中的 save() 方法。關(guān)于這一塊,大家可以自己嘗試一下,讓一個(gè)請(qǐng)求暫停然后看 Session 文件里數(shù)據(jù)有沒(méi)有變化,然后暫停完成之后再看一下就明白了。當(dāng)然,我們也可以手動(dòng)調(diào)用 save() 方法實(shí)時(shí)保存。

      響應(yīng)

      對(duì)于請(qǐng)求流程,大家已經(jīng)非常熟悉了,也了解過(guò)在控制器或者路由中,想要返回響應(yīng)的內(nèi)容,直接 retrun 就可以了,不過(guò)對(duì)于具體的響應(yīng)操作我們還是沒(méi)有進(jìn)行過(guò)深入的學(xué)習(xí)。今天就一起來(lái)學(xué)習(xí)一下響應(yīng)的具體內(nèi)容。

      添加響應(yīng)頭及 Cookie

      如果要返回響應(yīng)內(nèi)容,直接 return 數(shù)據(jù)就可以了,但如果想為響應(yīng)增加頭和 Cookie 信息的話,最簡(jiǎn)單的就是使用 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() 方法可以指定單個(gè)響應(yīng)頭,而 withHeaders() 則可以以數(shù)組的方式設(shè)置多個(gè)響應(yīng)頭。cookie() 方法則是設(shè)置 Cookie 的方法,它的參數(shù)和我們普通的 Cookie 操作函數(shù)的參數(shù)是一致的,后面的可選參數(shù)中也可以設(shè)置過(guò)期時(shí)間、HttpOnly 等內(nèi)容。

      重定向與文件下載

      對(duì)于重定向來(lái)說(shuō),我們可以看成是跳轉(zhuǎn)至某個(gè)頁(yè)面,可以直接寫(xiě)路由、使用路由別名,也可以直接跳轉(zhuǎn)到某個(gè)控制器方法。

      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;
      });

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

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

      文件下載有一個(gè)非常簡(jiǎn)單的函數(shù)就是直接使用 download() 函數(shù),里面指定文件路徑就可以了。同時(shí)還有別的方式可以實(shí)現(xiàn)文件的下載,文檔中寫(xiě)得很詳細(xì)了,這里就不多說(shuō)了。

      響應(yīng)流程

      對(duì)于響應(yīng)來(lái)說(shuō),通過(guò)查閱 response() 方法的實(shí)現(xiàn)就可以發(fā)現(xiàn)它返回的是一個(gè) vendor/laravel/framework/src/Illuminate/Http/Response.php 對(duì)象,而這個(gè)對(duì)象又是繼承自 Symfony 的 vendor/symfony/http-foundation/Response.php 。就和請(qǐng)求一樣,它的底層實(shí)現(xiàn)依然是 Symfony 框架中的響應(yīng)實(shí)現(xiàn)。

      首先到 public/index.php 入口文件中,我們會(huì)發(fā)現(xiàn)這樣的一段代碼。

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

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

      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() 方法在之前的 中間件 以及 服務(wù)容器 和 管道 相關(guān)的文章中都接觸過(guò),他就是我們請(qǐng)求處理的核心流程,在請(qǐng)求的最后,就會(huì)返回響應(yīng)結(jié)果。在 index.php 中,Kernel 執(zhí)行完 handle() 之后,會(huì)再調(diào)用一個(gè) send() 方法。這個(gè)方法存在于 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;
      }

      接下來(lái)我們?cè)龠M(jìn)入到 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é)

      今天學(xué)習(xí)了兩塊內(nèi)容,不過(guò)其實(shí)都和請(qǐng)求響應(yīng)有關(guān),Session 是非常常用的功能,響應(yīng)也是所有請(qǐng)求必不可少的。Session 之所以框架要重寫(xiě)一套而不用原生的,也是為了靈活起見(jiàn),我們不需要去 php.ini 配置文件修改 Session 相關(guān)的功能。而響應(yīng)則走的依然是 Symfony 的底層框架功能。就像 Laravel 的口號(hào)一樣,讓實(shí)現(xiàn)的代碼更優(yōu)雅,從而對(duì)這些功能又重新進(jìn)行更適合自己的封裝。

      參考文檔:

      https:///docs/laravel/8.x/session/9373

      https:///docs/laravel/8.x/responses/9370

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

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多