本文解決什麼問題
- 怎麼理解中介軟體,為什麼要中介軟體?
- 路由中介軟體和全域性中介軟體有什麼區別?
- 中介軟體的實現原理(原始碼解讀)
什麼是中介軟體
先貼出一張圖讓大家理解中介軟體
再結合官方文件給出的定義:中介軟體提供了一種方便的機制來過濾進入應用程式的 HTTP 請求,就比較好理解了。
其實中介軟體也可以這麼理解①類似於設計模式的裝飾器模式,意義在於我們可以在不改變核心/原有的功能的情況下,為其新增其他的功能;②物件導向中抽象封裝的思想,將相同的功能全部抽象封裝起來;
現在我們再帶著第2、3問題,全域性與路由中介軟體的區別,中介軟體的實現原理,下面來看原始碼分析。
中介軟體原始碼逐步解讀
生命週期的概括
Laravel程式的生命週期就是從客戶端接受到資料,然後處理資料返回一個響應的過程;
Laravel 應用的所有請求入口都是 public/index.php 檔案。
// 檔案 public/index.php
// 1. 引入composer自動載入功能
require __DIR__.'/../vendor/autoload.php';
// 2. 例項化了服務容器$app
$app = require_once __DIR__.'/../bootstrap/app.php';
// 3. 從服務容器中
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
// 4. 將獲取的請求資料作為引數,例項化了一個Illuminate\Http\Request的例項;
// 簡單的可以理解為, 將$_GET $_POST $_SERVER 等php能獲取到的資料進行處理
$request = Illuminate\Http\Request::capture()
// 5. 處理 請求例項$request 得到 響應例項 $response
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
從上述的功能簡單可以看出,我們中介軟體的處理是在第5步,因為中介軟體其實就是對request請求進行處理的嘛
ps:此文著重講解中介軟體的實現,以下貼出的原始碼會對次要程式碼省略,也會淡化其他功能(如:服務容器,Facade),以方便大家理解;
中介軟體實現相關原始碼
// 檔案 vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
public function handle($request)
{
try {
$response = $this->sendRequestThroughRouter($request);
}
return $response;
}
// 檔案 vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
protected function sendRequestThroughRouter($request)
{
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
// 檔案 vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
// 管道類
class Pipeline implements PipelineContract
{
protected $container;
protected $passable;
protected $pipes = [];
protected $method = 'handle';
public function __construct(Container $container = null)
{
$this->container = $container;
}
// 把$request例項放入到存到passable屬性內
public function send($passable)
{
$this->passable = $passable;
return $this;
}
// 把middleware存入到pipes屬性內
public function through($pipes)
{
$this->pipes = is_array($pipes) ? $pipes : func_get_args();
return $this;
}
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
try {
return $destination($passable);
} catch (Throwable $e) {
return $this->handleException($passable, $e);
}
};
}
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
try {
if (is_callable($pipe)) {
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
[$name, $parameters] = $this->parsePipeString($pipe);
$pipe = $this->getContainer()->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
$parameters = [$passable, $stack];
}
$carry = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
return $this->handleCarry($carry);
} catch (Throwable $e) {
return $this->handleException($passable, $e);
}
};
};
}
}
以上就是從index入口檔案,到實現中介軟體功能的核心原始碼;從上面的第二段程式碼可以看出,中介軟體實現的核心功能很簡單,就是Pipeline例項的三個鏈式呼叫;所以著重分析,Pipeline裡面三個function做了什麼事,如何實現前後置中介軟體?
send()/through()
其實不難看出,這兩個方法只是簡單的進行屬性設定。①把$request例項放入到存到passable屬性內;②把middleware中介軟體陣列存入到pipes屬性內。不用過多解釋
then()
由於篇幅限制,下面另起一文
手把手帶你理解中介軟體Class Pipeline - Function then()實現原理
路由中介軟體與全域性中介軟體
透過我們對then方法的瞭解,我們知道,then方法傳入的閉包$this->dispatchToRouter()是在前置全域性中介軟體和後置中介軟體的中間進行執行的。
那麼根據一層一層檢視呼叫最會發現
// 檔案 vendor/laravel/framework/src/Illuminate/Routing/Router.php
protected function runRouteWithinStack(Route $route, Request $request)
{
... ...
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}
其實路由中介軟體的實現和全域性中介軟體的實現方式沒有區別… …
總結
新增中介軟體的整個過程,就是透過send方法傳入請求,用though方法傳入全域性中介軟體,最後用then方法實現了一個遞迴的閉包函式,最終執行的過程。
本作品採用《CC 協議》,轉載必須註明作者和本文連結