摘要:解析出后將進(jìn)入應(yīng)用的請(qǐng)求對(duì)象傳遞給的方法,在方法負(fù)責(zé)處理流入應(yīng)用的請(qǐng)求對(duì)象并返回響應(yīng)對(duì)象。攜帶了本次迭代的值。通過(guò)這種方式讓請(qǐng)求對(duì)象依次流過(guò)了要通過(guò)的中間件,達(dá)到目的地的方法。
中間件(Middleware)在Laravel中起著過(guò)濾進(jìn)入應(yīng)用的HTTP請(qǐng)求對(duì)象(Request)和完善離開(kāi)應(yīng)用的HTTP響應(yīng)對(duì)象(Reponse)的作用, 而且可以通過(guò)應(yīng)用多個(gè)中間件來(lái)層層過(guò)濾請(qǐng)求、逐步完善相應(yīng)。這樣就做到了程序的解耦,如果沒(méi)有中間件那么我們必須在控制器中來(lái)完成這些步驟,這無(wú)疑會(huì)造成控制器的臃腫。
舉一個(gè)簡(jiǎn)單的例子,在一個(gè)電商平臺(tái)上用戶既可以是一個(gè)普通用戶在平臺(tái)上購(gòu)物也可以在開(kāi)店后是一個(gè)賣(mài)家用戶,這兩種用戶的用戶體系往往都是一套,那么在只有賣(mài)家用戶才能訪問(wèn)的控制器里我們只需要應(yīng)用兩個(gè)中間件來(lái)完成賣(mài)家用戶的身份認(rèn)證:
class MerchantController extends Controller$ { public function __construct() { $this->middleware("auth"); $this->middleware("mechatnt_auth"); } }
在auth中間件里做了通用的用戶認(rèn)證,成功后HTTP Request會(huì)走到merchant_auth中間件里進(jìn)行商家用戶信息的認(rèn)證,兩個(gè)中間件都通過(guò)后HTTP Request就能進(jìn)入到要去的控制器方法中了。利用中間件,我們就能把這些認(rèn)證代碼抽離到對(duì)應(yīng)的中間件中了,而且可以根據(jù)需求自由組合多個(gè)中間件來(lái)對(duì)HTTP Request進(jìn)行過(guò)濾。
再比如Laravel自動(dòng)給所有路由應(yīng)用的VerifyCsrfToken中間件,在HTTP Requst進(jìn)入應(yīng)用走過(guò)VerifyCsrfToken中間件時(shí)會(huì)驗(yàn)證Token防止跨站請(qǐng)求偽造,在Http Response 離開(kāi)應(yīng)用前會(huì)給響應(yīng)添加合適的Cookie。(laravel5.5開(kāi)始CSRF中間件只自動(dòng)應(yīng)用到web路由上)
上面例子中過(guò)濾請(qǐng)求的叫前置中間件,完善響應(yīng)的叫做后置中間件。用一張圖可以標(biāo)示整個(gè)流程:
上面概述了下中間件在laravel中的角色,以及什么類(lèi)型的代碼應(yīng)該從控制器挪到中間件里,至于如何定義和使用自己的laravel 中間件請(qǐng)參考官方文檔。
下面我們主要來(lái)看一下Laravel中是怎么實(shí)現(xiàn)中間件的,中間件的設(shè)計(jì)應(yīng)用了一種叫做裝飾器的設(shè)計(jì)模式,如果你還不知道什么是裝飾器模式可以查閱設(shè)計(jì)模式相關(guān)的書(shū),也可以簡(jiǎn)單參考下這篇文章。
Laravel實(shí)例化Application后,會(huì)從服務(wù)容器里解析出Http Kernel對(duì)象,通過(guò)類(lèi)的名字也能看出來(lái)Http Kernel就是Laravel里負(fù)責(zé)HTTP請(qǐng)求和響應(yīng)的核心。
/** * @var AppHttpKernel $kernel */ $kernel = $app->make(IlluminateContractsHttpKernel::class); $response = $kernel->handle( $request = IlluminateHttpRequest::capture() ); $response->send(); $kernel->terminate($request, $response);
在index.php里可以看到,從服務(wù)容器里解析出Http Kernel,因?yàn)樵?b>bootstrap/app.php里綁定了IlluminateContractsHttpKernel接口的實(shí)現(xiàn)類(lèi)AppHttpKernel所以$kernel實(shí)際上是AppHttpKernel類(lèi)的對(duì)象。
解析出Http Kernel后Laravel將進(jìn)入應(yīng)用的請(qǐng)求對(duì)象傳遞給Http Kernel的handle方法,在handle方法負(fù)責(zé)處理流入應(yīng)用的請(qǐng)求對(duì)象并返回響應(yīng)對(duì)象。
/** * 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; }
中間件過(guò)濾應(yīng)用的過(guò)程就發(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()); }
這個(gè)方法的前半部分是對(duì)Application進(jìn)行了初始化,在上一篇講解服務(wù)提供器的文章里有對(duì)這一部分的詳細(xì)講解。Laravel通過(guò)Pipeline(管道)對(duì)象來(lái)傳輸請(qǐng)求對(duì)象,在Pipeline中請(qǐng)求對(duì)象依次通過(guò)Http Kernel里定義的中間件的前置操作到達(dá)控制器的某個(gè)action或者直接閉包處理得到響應(yīng)對(duì)象。
看下Pipeline里這幾個(gè)方法:
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 就是要通過(guò)的中間件 $pipes = array_reverse($this->pipes); //$this->passable就是Request對(duì)象 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管道的終點(diǎn)或者叫目的地 protected function dispatchToRouter() { return function ($request) { $this->app->instance("request", $request); return $this->router->dispatch($request); }; }
上面的函數(shù)看起來(lái)比較暈,我們先來(lái)看下array_reduce里對(duì)它的callback函數(shù)參數(shù)的解釋?zhuān)?/p>
mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )array_reduce() 將回調(diào)函數(shù) callback 迭代地作用到 array 數(shù)組中的每一個(gè)單元中,從而將數(shù)組簡(jiǎn)化為單一的值。
callback ( mixed $carry , mixed $item )
carry
攜帶上次迭代里的值; 如果本次迭代是第一次,那么這個(gè)值是 initial。item 攜帶了本次迭代的值。
getInitialSlice方法,他的返回值是作為傳遞給callbakc函數(shù)的$carry參數(shù)的初始值,這個(gè)值現(xiàn)在是一個(gè)閉包,我把getInitialSlice和Http Kernel的dispatchToRouter這兩個(gè)方法合并一下,現(xiàn)在$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); };
接下來(lái)我們看看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的父類(lèi)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)) { //解析中間件名稱(chēng)和參數(shù) ("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這個(gè)方法的名稱(chēng)換成了carry, 兩者在邏輯上沒(méi)有區(qū)別,所以依然可以參照著5.5版本里中間件的代碼來(lái)看本文。
getSlice會(huì)返回一個(gè)閉包函數(shù), $stack在第一次調(diào)用getSlice時(shí)它的值是$firstSlice, 之后的調(diào)用中就它的值就是這里返回的值個(gè)閉包了:
$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返回的閉包里又會(huì)去調(diào)用父類(lèi)的getSlice方法,他返回的也是一個(gè)閉包,在閉包會(huì)里解析出中間件對(duì)象、中間件參數(shù)(無(wú)則為空數(shù)組), 然后把$passable(請(qǐng)求對(duì)象), $stack和中間件參數(shù)作為中間件handle方法的參數(shù)進(jìn)行調(diào)用。
上面封裝的有點(diǎn)復(fù)雜,我們簡(jiǎn)化一下,其實(shí)getSlice的返回值就是:
$stack = function ($passable) use ($stack, $pipe) { //解析中間件和中間件參數(shù),中間件參數(shù)用$parameter代表,無(wú)參數(shù)時(shí)為空數(shù)組 $parameters = array_merge([$passable, $stack], $parameters) return $pipe->handle($parameters) };
array_reduce每次調(diào)用callback返回的閉包都會(huì)作為參數(shù)$stack傳遞給下一次對(duì)callback的調(diào)用,array_reduce執(zhí)行完成后就會(huì)返回一個(gè)嵌套了多層閉包的閉包,每層閉包用到的外部變量$stack都是上一次之前執(zhí)行reduce返回的閉包,相當(dāng)于把中間件通過(guò)閉包層層包裹包成了一個(gè)洋蔥。
在then方法里,等到array_reduce執(zhí)行完返回最終結(jié)果后就會(huì)對(duì)這個(gè)洋蔥閉包進(jìn)行調(diào)用:
return call_user_func( array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable);
這樣就能依次執(zhí)行中間件handle方法,在handle方法里又會(huì)去再次調(diào)用之前說(shuō)的reduce包裝的洋蔥閉包剩余的部分,這樣一層層的把洋蔥剝開(kāi)直到最后。通過(guò)這種方式讓請(qǐng)求對(duì)象依次流過(guò)了要通過(guò)的中間件,達(dá)到目的地Http Kernel 的dispatchToRouter方法。
通過(guò)剝洋蔥的過(guò)程我們就能知道為什么在array_reduce之前要先對(duì)middleware數(shù)組進(jìn)行反轉(zhuǎn), 因?yàn)榘b是一個(gè)反向的過(guò)程, 數(shù)組$pipes中的第一個(gè)中間件會(huì)作為第一次reduce執(zhí)行的結(jié)果被包裝在洋蔥閉包的最內(nèi)層,所以只有反轉(zhuǎn)后才能保證初始定義的中間件數(shù)組中第一個(gè)中間件的handle方法會(huì)被最先調(diào)用。
上面說(shuō)了Pipeline傳送請(qǐng)求對(duì)象的目的地是Http Kernel 的dispatchToRouter方法,其實(shí)到遠(yuǎn)沒(méi)有到達(dá)最終的目的地,現(xiàn)在請(qǐng)求對(duì)象了只是剛通過(guò)了AppHttpKernel類(lèi)里$middleware屬性里羅列出的幾個(gè)中間件:
protected $middleware = [ IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode::class, IlluminateFoundationHttpMiddlewareValidatePostSize::class, AppHttpMiddlewareTrimStrings::class, IlluminateFoundationHttpMiddlewareConvertEmptyStringsToNull::class, AppHttpMiddlewareTrustProxies::class, ];
當(dāng)請(qǐng)求對(duì)象進(jìn)入Http Kernel的dispatchToRouter方法后,請(qǐng)求對(duì)象在被Router dispatch派發(fā)給路由時(shí)會(huì)進(jìn)行收集路由上應(yīng)用的中間件和控制器里應(yīng)用的中間件。
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; //收集路由和控制器里應(yīng)用的中間件 $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() ); }); } }
收集完路由和控制器里應(yīng)用的中間件后,依然是利用Pipeline對(duì)象來(lái)傳送請(qǐng)求對(duì)象通過(guò)收集上來(lái)的這些中間件然后到達(dá)最終的目的地,在那里會(huì)執(zhí)行路由對(duì)應(yīng)的控制器方法生成響應(yīng)對(duì)象,然后響應(yīng)對(duì)象會(huì)依次來(lái)通過(guò)上面應(yīng)用的所有中間件的后置操作,最終離開(kāi)應(yīng)用被發(fā)送給客戶端。
限于篇幅和為了文章的可讀性,收集路由和控制器中間件然后執(zhí)行路由對(duì)應(yīng)的處理方法的過(guò)程我就不在這里詳述了,感興趣的同學(xué)可以自己去看Router的源碼,本文的目的還是主要為了梳理laravel是如何設(shè)計(jì)中間件的以及如何執(zhí)行它們的,希望能對(duì)感興趣的朋友有幫助。
本文已經(jīng)收錄在系列文章Laravel源碼學(xué)習(xí)里,歡迎訪問(wèn)閱讀。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/28227.html
摘要:下面是剛才說(shuō)的這些步驟對(duì)應(yīng)的核心代碼收集路由和控制器里應(yīng)用的中間件我們?cè)谇懊娴奈恼吕镆呀?jīng)詳細(xì)的解釋過(guò)中間件和路由的原理了,接下來(lái)就看看當(dāng)請(qǐng)求最終找到了路由對(duì)應(yīng)的控制器方法后是如何為控制器方法注入正確的參數(shù)并調(diào)用控制器方法的。 控制器 控制器能夠?qū)⑾嚓P(guān)的請(qǐng)求處理邏輯組成一個(gè)單獨(dú)的類(lèi), 通過(guò)前面的路由和中間件兩個(gè)章節(jié)我們多次強(qiáng)調(diào)Laravel應(yīng)用的請(qǐng)求在進(jìn)入應(yīng)用后首現(xiàn)會(huì)通過(guò)Http Ker...
摘要:設(shè)置生成對(duì)象后就要執(zhí)行對(duì)象的方法了,該方法定義在類(lèi)中,其主要目的是對(duì)進(jìn)行微調(diào)使其能夠遵從協(xié)議。最后會(huì)把完整的響應(yīng)發(fā)送給客戶端。本文已經(jīng)收錄在系列文章源碼學(xué)習(xí)里,歡迎訪問(wèn)閱讀。 Response 前面兩節(jié)我們分別講了Laravel的控制器和Request對(duì)象,在講Request對(duì)象的那一節(jié)我們看了Request對(duì)象是如何被創(chuàng)建出來(lái)的以及它支持的方法都定義在哪里,講控制器時(shí)我們?cè)敿?xì)地描述了...
摘要:過(guò)去一年時(shí)間寫(xiě)了多篇文章來(lái)探討了我認(rèn)為的框架最核心部分的設(shè)計(jì)思路代碼實(shí)現(xiàn)。為了大家閱讀方便,我把這些源碼學(xué)習(xí)的文章匯總到這里。數(shù)據(jù)庫(kù)算法和數(shù)據(jù)結(jié)構(gòu)這些都是編程的內(nèi)功,只有內(nèi)功深厚了才能解決遇到的復(fù)雜問(wèn)題。 過(guò)去一年時(shí)間寫(xiě)了20多篇文章來(lái)探討了我認(rèn)為的Larave框架最核心部分的設(shè)計(jì)思路、代碼實(shí)現(xiàn)。通過(guò)更新文章自己在軟件設(shè)計(jì)、文字表達(dá)方面都有所提高,在剛開(kāi)始決定寫(xiě)Laravel源碼分析地...
摘要:調(diào)用了的可以看出,所有服務(wù)提供器都在配置文件文件的數(shù)組中。啟動(dòng)的啟動(dòng)由類(lèi)負(fù)責(zé)引導(dǎo)應(yīng)用的屬性中記錄的所有服務(wù)提供器,就是依次調(diào)用這些服務(wù)提供器的方法,引導(dǎo)完成后就代表應(yīng)用正式啟動(dòng)了,可以開(kāi)始處理請(qǐng)求了。 服務(wù)提供器是所有 Laravel 應(yīng)用程序引導(dǎo)中心。你的應(yīng)用程序自定義的服務(wù)、第三方資源包提供的服務(wù)以及 Laravel 的所有核心服務(wù)都是通過(guò)服務(wù)提供器進(jìn)行注冊(cè)(register)和引...
摘要:年月日階段劃分請(qǐng)求到響應(yīng)的整個(gè)執(zhí)行階段歸納為個(gè)程序啟動(dòng)準(zhǔn)備階段文件自動(dòng)加載服務(wù)容器實(shí)例化基礎(chǔ)服務(wù)提供者的注冊(cè)核心類(lèi)的實(shí)例化請(qǐng)求實(shí)例化階段實(shí)例化實(shí)例請(qǐng)求處理階段準(zhǔn)備請(qǐng)求處理的環(huán)境將請(qǐng)求實(shí)例通過(guò)中間件處理及通過(guò)路由和控制器的分發(fā)控制響應(yīng)發(fā)送和 Last-Modified: 2019年5月10日16:19:07 階段劃分 Laravel 5.5請(qǐng)求到響應(yīng)的整個(gè)執(zhí)行階段歸納為 4 個(gè): ...
閱讀 1947·2023-04-26 02:32
閱讀 635·2021-11-18 13:12
閱讀 2516·2021-10-20 13:48
閱讀 2609·2021-10-14 09:43
閱讀 3914·2021-10-11 10:58
閱讀 3724·2021-09-30 10:00
閱讀 2997·2019-08-30 15:53
閱讀 3547·2019-08-30 15:53