摘要:解析出后將進入應用的請求對象傳遞給的方法,在方法負責處理流入應用的請求對象并返回響應對象。攜帶了本次迭代的值。通過這種方式讓請求對象依次流過了要通過的中間件,達到目的地的方法。
中間件(Middleware)在Laravel中起著過濾進入應用的HTTP請求對象(Request)和完善離開應用的HTTP響應對象(Reponse)的作用, 而且可以通過應用多個中間件來層層過濾請求、逐步完善相應。這樣就做到了程序的解耦,如果沒有中間件那么我們必須在控制器中來完成這些步驟,這無疑會造成控制器的臃腫。
舉一個簡單的例子,在一個電商平臺上用戶既可以是一個普通用戶在平臺上購物也可以在開店后是一個賣家用戶,這兩種用戶的用戶體系往往都是一套,那么在只有賣家用戶才能訪問的控制器里我們只需要應用兩個中間件來完成賣家用戶的身份認證:
class MerchantController extends Controller$ { public function __construct() { $this->middleware("auth"); $this->middleware("mechatnt_auth"); } }
在auth中間件里做了通用的用戶認證,成功后HTTP Request會走到merchant_auth中間件里進行商家用戶信息的認證,兩個中間件都通過后HTTP Request就能進入到要去的控制器方法中了。利用中間件,我們就能把這些認證代碼抽離到對應的中間件中了,而且可以根據需求自由組合多個中間件來對HTTP Request進行過濾。
再比如Laravel自動給所有路由應用的VerifyCsrfToken中間件,在HTTP Requst進入應用走過VerifyCsrfToken中間件時會驗證Token防止跨站請求偽造,在Http Response 離開應用前會給響應添加合適的Cookie。(laravel5.5開始CSRF中間件只自動應用到web路由上)
上面例子中過濾請求的叫前置中間件,完善響應的叫做后置中間件。用一張圖可以標示整個流程:
上面概述了下中間件在laravel中的角色,以及什么類型的代碼應該從控制器挪到中間件里,至于如何定義和使用自己的laravel 中間件請參考官方文檔。
下面我們主要來看一下Laravel中是怎么實現中間件的,中間件的設計應用了一種叫做裝飾器的設計模式,如果你還不知道什么是裝飾器模式可以查閱設計模式相關的書,也可以簡單參考下這篇文章。
Laravel實例化Application后,會從服務容器里解析出Http Kernel對象,通過類的名字也能看出來Http Kernel就是Laravel里負責HTTP請求和響應的核心。
/** * @var AppHttpKernel $kernel */ $kernel = $app->make(IlluminateContractsHttpKernel::class); $response = $kernel->handle( $request = IlluminateHttpRequest::capture() ); $response->send(); $kernel->terminate($request, $response);
在index.php里可以看到,從服務容器里解析出Http Kernel,因為在bootstrap/app.php里綁定了IlluminateContractsHttpKernel接口的實現類AppHttpKernel所以$kernel實際上是AppHttpKernel類的對象。
解析出Http Kernel后Laravel將進入應用的請求對象傳遞給Http Kernel的handle方法,在handle方法負責處理流入應用的請求對象并返回響應對象。
/** * Handle an incoming HTTP request. * * @param IlluminateHttpRequest $request * @return IlluminateHttpResponse */ public function handle($request) { try { $request->enableHttpMethodParameterOverride(); $response = $this->sendRequestThroughRouter($request); } catch (Exception $e) { $this->reportException($e); $response = $this->renderException($request, $e); } catch (Throwable $e) { $this->reportException($e = new FatalThrowableError($e)); $response = $this->renderException($request, $e); } $this->app["events"]->dispatch( new EventsRequestHandled($request, $response) ); return $response; }
中間件過濾應用的過程就發(fā)生在$this->sendRequestThroughRouter($request)里:
/** * Send the given request through the middleware / router. * * @param IlluminateHttpRequest $request * @return IlluminateHttpResponse */ protected function sendRequestThroughRouter($request) { $this->app->instance("request", $request); Facade::clearResolvedInstance("request"); $this->bootstrap(); return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); }
這個方法的前半部分是對Application進行了初始化,在上一篇講解服務提供器的文章里有對這一部分的詳細講解。Laravel通過Pipeline(管道)對象來傳輸請求對象,在Pipeline中請求對象依次通過Http Kernel里定義的中間件的前置操作到達控制器的某個action或者直接閉包處理得到響應對象。
看下Pipeline里這幾個方法:
public function send($passable) { $this->passable = $passable; return $this; } public function through($pipes) { $this->pipes = is_array($pipes) ? $pipes : func_get_args(); return $this; } public function then(Closure $destination) { $firstSlice = $this->getInitialSlice($destination); //pipes 就是要通過的中間件 $pipes = array_reverse($this->pipes); //$this->passable就是Request對象 return call_user_func( array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable ); } protected function getInitialSlice(Closure $destination) { return function ($passable) use ($destination) { return call_user_func($destination, $passable); }; } //Http Kernel的dispatchToRouter是Piple管道的終點或者叫目的地 protected function dispatchToRouter() { return function ($request) { $this->app->instance("request", $request); return $this->router->dispatch($request); }; }
上面的函數看起來比較暈,我們先來看下array_reduce里對它的callback函數參數的解釋:
mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )array_reduce() 將回調函數 callback 迭代地作用到 array 數組中的每一個單元中,從而將數組簡化為單一的值。
callback ( mixed $carry , mixed $item )
carry
攜帶上次迭代里的值; 如果本次迭代是第一次,那么這個值是 initial。item 攜帶了本次迭代的值。
getInitialSlice方法,他的返回值是作為傳遞給callbakc函數的$carry參數的初始值,這個值現在是一個閉包,我把getInitialSlice和Http Kernel的dispatchToRouter這兩個方法合并一下,現在$firstSlice的值為:
$destination = function ($request) { $this->app->instance("request", $request); return $this->router->dispatch($request); }; $firstSlice = function ($passable) use ($destination) { return call_user_func($destination, $passable); };
接下來我們看看array_reduce的callback:
//Pipeline protected function getSlice() { return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { try { $slice = parent::getSlice(); return call_user_func($slice($stack, $pipe), $passable); } catch (Exception $e) { return $this->handleException($passable, $e); } catch (Throwable $e) { return $this->handleException($passable, new FatalThrowableError($e)); } }; }; } //Pipleline的父類BasePipeline的getSlice方法 protected function getSlice() { return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { if ($pipe instanceof Closure) { return call_user_func($pipe, $passable, $stack); } elseif (! is_object($pipe)) { //解析中間件名稱和參數 ("throttle:60,1") list($name, $parameters) = $this->parsePipeString($pipe); $pipe = $this->container->make($name); $parameters = array_merge([$passable, $stack], $parameters); } else{ $parameters = [$passable, $stack]; } //$this->method = handle return call_user_func_array([$pipe, $this->method], $parameters); }; }; }
注:在Laravel5.5版本里 getSlice這個方法的名稱換成了carry, 兩者在邏輯上沒有區(qū)別,所以依然可以參照著5.5版本里中間件的代碼來看本文。
getSlice會返回一個閉包函數, $stack在第一次調用getSlice時它的值是$firstSlice, 之后的調用中就它的值就是這里返回的值個閉包了:
$stack = function ($passable) use ($stack, $pipe) { try { $slice = parent::getSlice(); return call_user_func($slice($stack, $pipe), $passable); } catch (Exception $e) { return $this->handleException($passable, $e); } catch (Throwable $e) { return $this->handleException($passable, new FatalThrowableError($e)); } };
getSlice返回的閉包里又會去調用父類的getSlice方法,他返回的也是一個閉包,在閉包會里解析出中間件對象、中間件參數(無則為空數組), 然后把$passable(請求對象), $stack和中間件參數作為中間件handle方法的參數進行調用。
上面封裝的有點復雜,我們簡化一下,其實getSlice的返回值就是:
$stack = function ($passable) use ($stack, $pipe) { //解析中間件和中間件參數,中間件參數用$parameter代表,無參數時為空數組 $parameters = array_merge([$passable, $stack], $parameters) return $pipe->handle($parameters) };
array_reduce每次調用callback返回的閉包都會作為參數$stack傳遞給下一次對callback的調用,array_reduce執(zhí)行完成后就會返回一個嵌套了多層閉包的閉包,每層閉包用到的外部變量$stack都是上一次之前執(zhí)行reduce返回的閉包,相當于把中間件通過閉包層層包裹包成了一個洋蔥。
在then方法里,等到array_reduce執(zhí)行完返回最終結果后就會對這個洋蔥閉包進行調用:
return call_user_func( array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable);
這樣就能依次執(zhí)行中間件handle方法,在handle方法里又會去再次調用之前說的reduce包裝的洋蔥閉包剩余的部分,這樣一層層的把洋蔥剝開直到最后。通過這種方式讓請求對象依次流過了要通過的中間件,達到目的地Http Kernel 的dispatchToRouter方法。
通過剝洋蔥的過程我們就能知道為什么在array_reduce之前要先對middleware數組進行反轉, 因為包裝是一個反向的過程, 數組$pipes中的第一個中間件會作為第一次reduce執(zhí)行的結果被包裝在洋蔥閉包的最內層,所以只有反轉后才能保證初始定義的中間件數組中第一個中間件的handle方法會被最先調用。
上面說了Pipeline傳送請求對象的目的地是Http Kernel 的dispatchToRouter方法,其實到遠沒有到達最終的目的地,現在請求對象了只是剛通過了AppHttpKernel類里$middleware屬性里羅列出的幾個中間件:
protected $middleware = [ IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode::class, IlluminateFoundationHttpMiddlewareValidatePostSize::class, AppHttpMiddlewareTrimStrings::class, IlluminateFoundationHttpMiddlewareConvertEmptyStringsToNull::class, AppHttpMiddlewareTrustProxies::class, ];
當請求對象進入Http Kernel的dispatchToRouter方法后,請求對象在被Router dispatch派發(fā)給路由時會進行收集路由上應用的中間件和控制器里應用的中間件。
namespace IlluminateFoundationHttp; class Kernel implements KernelContract { protected function dispatchToRouter() { return function ($request) { $this->app->instance("request", $request); return $this->router->dispatch($request); }; } } namespace IlluminateRouting; class Router implements RegistrarContract, BindingRegistrar { public function dispatch(Request $request) { $this->currentRequest = $request; return $this->dispatchToRoute($request); } public function dispatchToRoute(Request $request) { return $this->runRoute($request, $this->findRoute($request)); } protected function runRoute(Request $request, Route $route) { $request->setRouteResolver(function () use ($route) { return $route; }); $this->events->dispatch(new EventsRouteMatched($route, $request)); return $this->prepareResponse($request, $this->runRouteWithinStack($route, $request) ); } protected function runRouteWithinStack(Route $route, Request $request) { $shouldSkipMiddleware = $this->container->bound("middleware.disable") && $this->container->make("middleware.disable") === true; //收集路由和控制器里應用的中間件 $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route); return (new Pipeline($this->container)) ->send($request) ->through($middleware) ->then(function ($request) use ($route) { return $this->prepareResponse( $request, $route->run() ); }); } }
收集完路由和控制器里應用的中間件后,依然是利用Pipeline對象來傳送請求對象通過收集上來的這些中間件然后到達最終的目的地,在那里會執(zhí)行路由對應的控制器方法生成響應對象,然后響應對象會依次來通過上面應用的所有中間件的后置操作,最終離開應用被發(fā)送給客戶端。
限于篇幅和為了文章的可讀性,收集路由和控制器中間件然后執(zhí)行路由對應的處理方法的過程我就不在這里詳述了,感興趣的同學可以自己去看Router的源碼,本文的目的還是主要為了梳理laravel是如何設計中間件的以及如何執(zhí)行它們的,希望能對感興趣的朋友有幫助。
本文已經收錄在系列文章Laravel源碼學習里,歡迎訪問閱讀。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/28227.html
摘要:下面是剛才說的這些步驟對應的核心代碼收集路由和控制器里應用的中間件我們在前面的文章里已經詳細的解釋過中間件和路由的原理了,接下來就看看當請求最終找到了路由對應的控制器方法后是如何為控制器方法注入正確的參數并調用控制器方法的。 控制器 控制器能夠將相關的請求處理邏輯組成一個單獨的類, 通過前面的路由和中間件兩個章節(jié)我們多次強調Laravel應用的請求在進入應用后首現會通過Http Ker...
摘要:設置生成對象后就要執(zhí)行對象的方法了,該方法定義在類中,其主要目的是對進行微調使其能夠遵從協(xié)議。最后會把完整的響應發(fā)送給客戶端。本文已經收錄在系列文章源碼學習里,歡迎訪問閱讀。 Response 前面兩節(jié)我們分別講了Laravel的控制器和Request對象,在講Request對象的那一節(jié)我們看了Request對象是如何被創(chuàng)建出來的以及它支持的方法都定義在哪里,講控制器時我們詳細地描述了...
摘要:過去一年時間寫了多篇文章來探討了我認為的框架最核心部分的設計思路代碼實現。為了大家閱讀方便,我把這些源碼學習的文章匯總到這里。數據庫算法和數據結構這些都是編程的內功,只有內功深厚了才能解決遇到的復雜問題。 過去一年時間寫了20多篇文章來探討了我認為的Larave框架最核心部分的設計思路、代碼實現。通過更新文章自己在軟件設計、文字表達方面都有所提高,在剛開始決定寫Laravel源碼分析地...
摘要:調用了的可以看出,所有服務提供器都在配置文件文件的數組中。啟動的啟動由類負責引導應用的屬性中記錄的所有服務提供器,就是依次調用這些服務提供器的方法,引導完成后就代表應用正式啟動了,可以開始處理請求了。 服務提供器是所有 Laravel 應用程序引導中心。你的應用程序自定義的服務、第三方資源包提供的服務以及 Laravel 的所有核心服務都是通過服務提供器進行注冊(register)和引...
摘要:年月日階段劃分請求到響應的整個執(zhí)行階段歸納為個程序啟動準備階段文件自動加載服務容器實例化基礎服務提供者的注冊核心類的實例化請求實例化階段實例化實例請求處理階段準備請求處理的環(huán)境將請求實例通過中間件處理及通過路由和控制器的分發(fā)控制響應發(fā)送和 Last-Modified: 2019年5月10日16:19:07 階段劃分 Laravel 5.5請求到響應的整個執(zhí)行階段歸納為 4 個: ...
閱讀 1828·2023-04-26 02:32
閱讀 576·2021-11-18 13:12
閱讀 2458·2021-10-20 13:48
閱讀 2528·2021-10-14 09:43
閱讀 3840·2021-10-11 10:58
閱讀 3516·2021-09-30 10:00
閱讀 2943·2019-08-30 15:53
閱讀 3496·2019-08-30 15:53