当前位置: 首页 > 后端技术 > PHP

Laravel中间件原理

时间:2023-03-29 16:58:44 PHP

介绍Laravel中间件提供了一种方便的机制来过滤进入应用程序的HTTP请求,例如ValidatePostSize用于验证POST请求体的大小,ThrottleRequests用于限制请求频率等。那么如何实现呢?Laravel的中间件好用吗?启动流程说Laravel中间件之前,先来看看laravel的启动流程首先,入口文件index.php加载autoload和引导文件bootstraprequire__DIR__.'/../bootstrap/autoload.php';$app=require_once__DIR__.'/../bootstrap/app.php';并在bootstrap/app.php文件中初始化了Application实例$app=newIlluminate\Foundation\Application(realpath(__DIR__.'/../'));我们先跳过如何初始化Application(后面会有简单介绍稍后),然后返回入口文件(index.php),通过从Application实例$app获取HttpKernel对象执行handle方法,换取响应。$kernel=$app->make(Illuminate\Contracts\Http\Kernel::class);$response=$kernel->handle($request=Illuminate\Http\Request::capture());$response->send();$kernel->terminate($request,$response);收到响应后,将响应内容返回给Client,进行后续操作(终止,如关闭会话等)。实例化应用:Laravel的容器不是我这次要讲的重点。这里简单介绍一下。在初始化Application(启动容器)时,Laravel主要做了三件事。注册BasicBindingRegistrationBasicServiceProviderRegistrationContainerCoreAlias注册完成后,我们可以直接从容器中获取需要的对象(比如Illuminate\\Contracts\\Http\\Kernel),哪怕是一个Interface。在获取Illuminate\Contracts\Http\Kernel类时,我们获取到的真实实例是AppHttpKernel//bootstrap/app.php$app->singleton(Illuminate\Contracts\Http\Kernel::class,App\Http\Kernel::班级);Laravel容器,请参考神奇的服务容器Handle从容器中获取到HttpKernel对象后,Laravel执行kernel->handle换取一个响应对象。//Illuminate\Foundation\Http\Kernel.phppublicfunctionhandle($request){$request->enableHttpMethodParameterOverride();$response=$this->sendRequestThroughRouter($request);//...}enableHttpMethodParameterOverridemethod启用方法参数覆盖,即可以在POST请求中添加_method参数伪造HTTP方法(例如在post中添加_method=DELETE构造HTTPDELETE请求)。Laravel然后通过管道流式传输请求对象(request)。受保护的函数sendRequestThroughRouter($request){return(newPipeline($this->app))->send($request)->through($this->app->shouldSkipMiddleware()?[]:$this->middleware)->then($this->dispatchToRouter());}/***获取路由调度回调。**@return\Closure*/protectedfunctiondispatchToRouter(){returnfunction($request){$this->app->instance('request',$request);}返回$this->router->dispatch($request);};}Pipeline是laravel的管道操作类。在这个方法中,我的理解是:通过middleware中间件数组发送一个$request对象,最后执行dispatchToRouter方法。注意这里的中间件只是全局中间件。即先让Request通过全局中间件,然后在路由转发($this->dispatchToRouter()),再通过路由中间件和中间件组。那么,至此,Laravel的请求就交给了Pipeline管理,我们来看看这个Pipeline是如何处理的。//Illuminate\Pipeline\Pipeline.phppublicfunctionthen(Closure$destination){$pipeline=array_reduce(array_reverse($this->pipes),$this->carry(),$this->prepareDestination($destination));返回$pipeline($this->passable);}protectedfunctionprepareDestination(Closure$destination){returnfunction($passable)use($destination){return$destination($passable);};}protectedfunctioncarry(){returnfunction($stack,$pipe){返回函数($passable)use($stack,$pipe){if($pipeinstanceofClosure){return$pipe($passable,$堆);}elseif(!is_object($pipe)){list($name,$parameters)=$this->parsePipeString($pipe);$pipe=$this->getContainer()->make($name);$parameters=array_merge([$passable,$stack],$parameters);}else{$parameters=[$passable,$stack];}返回$pipe->{$this->method}(...$parameters);};};}让我们来看看最重要的then方法。在这个方法中,$destination代表最后要通过pipelineClosure执行的事情(也就是上面提到的dispatchToRouter方法)passable代表通过pipeline传递过来的对象Request。PHP内置方法array_reduce将所有要通过carry方法传递的中间件($this->pipes)(当$this->pipes不为空时)压缩成一个Closure。最后,执行prepareDestination。array_reduce($pipes,callback($stack,$pipe),$destination),当pipes为空时,直接执行destination,否则将所有$pipes压缩成一个Closure,最后执行destination。比如我有两个中间件Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,App\Http\Middleware\AllowOrigin::class,//当自定义中间件通过array_reduce方法传递这两个中间件时,返回压缩后的Closure如下:Closure一共有三层,前两层是两个中间件,最后一层是要执行的Closure(也就是上面提到的dispatchToRouter方法)。//Middlewarehandlepublicfunctionhandle($request,Closure$next){}在第一个传递的中间件(这里是CheckForMaintenanceMode)的handle方法中,在第二个传递的中间件中dump($next)如下(里面有两个total,这里是handle方法中的AllowOrigin),dump($next)如下。可以看出,当中间件执行到$next($request)时,说明中间件已经正常通过了,有望继续执行接下来的中间件。直到所有的中间件都执行完,最后执行最终目的地(也就是上面的dispatchToRouter方法)。如果上面的array_reduce比较难理解,可以参考这篇文章。Laravel中PHP内置函数array_reduce的使用。中间件的大致流程是一样的,都是通过中间件组和路由中间件,都是使用管道流操作。具体可以参考源码Illuminate\Routing\Router->runRouteWithinStack