이 글에서는 라라벨 미들웨어(Middleware)의 핵심 해석을 주로 공유합니다. 라라벨의 미들웨어(Middleware)는 애플리케이션에 들어가는 HTTP 요청 객체(Request)를 필터링하고 HTTP 응답 객체(Reponse)를 개선하는 역할을 합니다. 기능을 떠나 요청을 계층별로 필터링하고 여러 미들웨어를 적용하여 점차적으로 응답을 향상시킬 수 있습니다. 이렇게 하면 프로그램이 분리됩니다. 미들웨어가 없으면 컨트롤러에서 이러한 단계를 완료해야 하며, 이는 의심할 여지 없이 컨트롤러를 비대하게 만듭니다.
간단히 예를 들자면, 전자상거래 플랫폼에서 사용자는 플랫폼에서 쇼핑을 하는 일반 사용자일 수도 있고, 매장을 오픈한 후의 판매자 사용자일 수도 있습니다. 이 두 유형의 사용자의 사용자 시스템은 종종 동일합니다. 따라서 판매자 사용자만 접근할 수 있는 컨트롤러에서는 판매자 사용자의 신원 인증을 완료하기 위해 두 개의 미들웨어만 적용하면 됩니다.
class MerchantController extends Controller{ public function __construct() { $this->middleware('auth'); $this->middleware('mechatnt_auth'); } }
인증에서 범용 사용자 인증을 수행했습니다 미들웨어 성공 후 HTTP 요청은 판매자 사용자 정보를 인증하기 위해 Merchant_auth 미들웨어로 이동합니다. 두 미들웨어가 모두 전달되면 HTTP 요청이 원하는 컨트롤러 메서드를 입력할 수 있습니다. 미들웨어를 사용하면 이러한 인증 코드를 해당 미들웨어로 추출할 수 있으며 필요에 따라 여러 미들웨어를 자유롭게 결합하여 HTTP 요청을 필터링할 수 있습니다.
또 다른 예는 Laravel이 모든 라우팅 애플리케이션에 자동으로 제공하는 VerifyCsrfToken
미들웨어입니다. HTTP 요청이 애플리케이션에 들어가서 VerifyCsrfToken
미들웨어를 통과하면 토큰이 확인됩니다. 사이트 간 요청 위조를 방지합니다. Http 응답은 애플리케이션을 떠나기 전에 응답에 적절한 쿠키를 추가합니다. (laravel 5.5부터는 CSRF 미들웨어가 웹 라우팅에만 자동으로 적용됩니다.) VerifyCsrfToken
中间件,在HTTP Requst进入应用走过VerifyCsrfToken
中间件时会验证Token防止跨站请求伪造,在Http Response 离开应用前会给响应添加合适的Cookie。(laravel5.5开始CSRF中间件只自动应用到web路由上)
上面例子中过滤请求的叫前置中间件,完善响应的叫做后置中间件。用一张图可以标示整个流程:
上面概述了下中间件在laravel中的角色,以及什么类型的代码应该从控制器挪到中间件里,至于如何定义和使用自己的laravel 中间件请参考官方文档。
下面我们主要来看一下Laravel中是怎么实现中间件的,中间件的设计应用了一种叫做装饰器的设计模式。
Laravel实例化Application后,会从服务容器里解析出Http Kernel对象,通过类的名字也能看出来Http Kernel就是Laravel里负责HTTP请求和响应的核心。
/** * @var \App\Http\Kernel $kernel */$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( $request = Illuminate\Http\Request::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 \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */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 Events\RequestHandled($request, $response) ); return $response; }
中间件过滤应用的过程就发生在$this->sendRequestThroughRouter($request)
위 내용은 라라벨에서 미들웨어의 역할과 컨트롤러에서 미들웨어로 어떤 유형의 코드를 옮겨야 하는지에 대해 간략하게 설명하고 있습니다. 공식 문서를 참조하세요. 이제 Laravel에서 미들웨어를 구현하는 방법을 주로 살펴 보겠습니다. 미들웨어의 디자인에는 데코레이터라는 디자인 패턴이 적용됩니다. Laravel은 애플리케이션을 인스턴스화한 후 서비스 컨테이너에서 Http Kernel 객체를 구문 분석합니다. 이는 Http Kernel이 Laravel에서 HTTP 요청 및 응답을 담당하는 핵심이라는 것을 클래스 이름에서도 알 수 있습니다.
/** * Send the given request through the middleware / router. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */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()); }
Http 커널이 bootstrap/app.php
에 있기 때문에 서비스 컨테이너에서 구문 분석되는 것을 index.php
에서 볼 수 있습니다. > IlluminateContractsHttpKernel
인터페이스의 구현 클래스 AppHttpKernel
가 이에 바인딩되므로 $kernel은 실제로 AppHttpKernel
클래스의 객체입니다.
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); }; }미들웨어 필터링 적용 프로세스는
$this->sendRequestThroughRouter($request)
에서 발생합니다. 🎜🎜🎜🎜 🎜🎜 🎜🎜🎜rrre 에에🎜 첫 번째 이 메소드의 절반은 애플리케이션을 초기화하는 부분입니다. 이 부분은 서비스 제공자를 설명하는 이전 기사에서 자세히 설명했습니다. Laravel은 Pipeline 객체를 통해 요청 객체를 전송합니다. Pipeline에서는 요청 객체가 Http Kernel에 정의된 미들웨어의 전처리 또는 응답 객체를 얻기 위한 직접 클로저 처리를 통해 컨트롤러의 작업에 도달합니다. 🎜🎜파이프라인에서 다음 메서드를 살펴보세요. 🎜mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] ) array_reduce() 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。 callback ( mixed $carry , mixed $item )carry携带上次迭代里的值; 如果本次迭代是第一次,那么这个值是 initial。item 携带了本次迭代的值。🎜위 함수는 먼저 array_reduce의 콜백 함수 매개변수에 대한 설명을 살펴보겠습니다. 🎜🎜
$destination = function ($request) { $this->app->instance('request', $request); return $this->router->dispatch($request); }; $firstSlice = function ($passable) use ($destination) { return call_user_func($destination, $passable); };🎜🎜getInitialSlice 메서드는 반환 값입니다. callbakc 함수에 전달된 $carray 매개변수는 이제 getInitialSlice와 Http Kernel의 dispatchToRouter의 두 가지 메소드를 병합했습니다. 이제 $firstSlice의 값은 다음과 같습니다. 🎜🎜🎜🎜 🎜🎜🎜
//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); }; }; }🎜다음을 살펴보겠습니다. array_reduce의 콜백에서:🎜
//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这个方法的名称换成了carray, 两者在逻辑上没有区别,所以依然可以参照着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执行完成后就会返回一个嵌套了多层闭包的闭包,每层闭包用到的外部变量$stack都是上一次之前执行reduce返回的闭包,相当于把中间件通过闭包层层包裹包成了一个洋葱。
在then方法里,等到array_reduce执行完返回最终结果后就会对这个洋葱闭包进行调用:
return call_user_func( array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable);
这样就能依次执行中间件handle方法,在handle方法里又会去再次调用之前说的reduce包装的洋葱闭包剩余的部分,这样一层层的把洋葱剥开直到最后。通过这种方式让请求对象依次流过了要通过的中间件,达到目的地Http Kernel 的dispatchToRouter
方法。
通过剥洋葱的过程我们就能知道为什么在array_reduce之前要先对middleware数组进行反转, 因为包装是一个反向的过程, 数组$pipes中的第一个中间件会作为第一次reduce执行的结果被包装在洋葱闭包的最内层,所以只有反转后才能保证初始定义的中间件数组中第一个中间件的handle方法会被最先调用。
上面说了Pipeline传送请求对象的目的地是Http Kernel 的dispatchToRouter
方法,其实到远没有到达最终的目的地,现在请求对象了只是刚通过了\App\Http\Kernel
类里$middleware
属性里罗列出的几个中间件:
protected $middleware = [ \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, \App\Http\Middleware\TrustProxies::class, ];
当请求对象进入dispatchToRouter
方法后,请求对象在被Router dispatch派发给路由时会进行收集路由上应用的中间件和控制器里应用的中间件。
public function dispatch(Request $request){ $this->currentRequest = $request; $response = $this->dispatchToRoute($request); return $this->prepareResponse($request, $response); }public function dispatchToRoute(Request $request){ return $this->runRoute($request, $this->findRoute($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对象来传送请求对象通过收集上来的这些中间件然后到达最终的目的地,在这里会执行路由对应的控制器方法生成响应对象,然后响应对象会依次来通过上面应用的所有中间件的后置操作,最终离开应用被发送给客户端。
限于篇幅和为了文章的可读性,收集路由和控制器中间件然后执行路由对应的处理方法的过程我就不在这里详述了,感兴趣的同学可以自己去看Router的源码,本文的目的还是主要为了梳理laravel是如何设计中间件的以及如何执行它们的,希望能对感兴趣的朋友有帮助。
相关推荐:
laravel中间件中的Closure $next是什么意思
위 내용은 Laravel 미들웨어(Middleware)의 핵심 해석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!