라라벨에서는 미들웨어는 이름에서 알 수 있듯이 요청 데이터를 가로채서 데이터를 검증하고 요청과 응답 간의 논리적 처리를 거쳐 다음 미들웨어로의 진입 허용 여부를 결정하는 역할을 합니다. 프리픽스 미들웨어와 포스트 미들웨어는 권한 인증, 로깅 등에 사용될 수 있습니다.
라라벨의 미들웨어(Middleware)는 애플리케이션에 들어오는 HTTP 요청 객체(Request)를 필터링하고 HTTP 나가기를 개선하는 역할을 합니다. 애플리케이션 응답 객체(Reponse)의 역할과 여러 미들웨어의 애플리케이션은 요청을 계층별로 필터링하고 점차적으로 응답을 향상시킬 수 있습니다. 이렇게 하면 프로그램이 분리됩니다. 미들웨어가 없으면 컨트롤러에서 이러한 단계를 완료해야 하며, 이는 의심할 여지 없이 컨트롤러를 비대하게 만듭니다.
간단한 예를 들자면 전자상거래 플랫폼에서 사용자는 플랫폼에서 쇼핑을 하는 일반 사용자일 수도 있고, 매장을 오픈한 후의 판매자 사용자일 수도 있습니다. users는 세트인 경우가 많으므로 판매자 사용자만 액세스할 수 있는 컨트롤러에서는 판매자 사용자의 신원 인증을 완료하기 위해 두 개의 미들웨어만 적용하면 됩니다.
class MerchantController extends Controller$ { public function __construct() { $this->middleware('auth'); $this->middleware('mechatnt_auth'); } }
범용 사용자 만들기 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에서 미들웨어를 구현하는 방법을 살펴보겠습니다. 미들웨어의 디자인은 데코레이터라는 디자인 패턴을 적용합니다. 아직 데코레이터 패턴이 무엇인지 모르신다면 확인해 보세요. 디자인 패턴과 관련된 책은 이 글을 참고하셔도 됩니다.
Laravel은 애플리케이션을 인스턴스화한 후 서비스 컨테이너에서 Http Kernel 객체를 구문 분석합니다. 이는 Http Kernel이 Laravel에서 HTTP 요청 및 응답을 담당하는 핵심이라는 것을 클래스 이름에서도 알 수 있습니다. .이 방법의 전반부 그 중 일부는 애플리케이션을 초기화하는 것입니다. 이 부분은 서비스 공급자를 설명하는 이전 기사에서 자세히 설명했습니다. Laravel은 Pipeline 객체를 통해 요청 객체를 전송합니다. Pipeline에서는 요청 객체가 Http Kernel에 정의된 미들웨어의 전처리 또는 응답 객체를 얻기 위한 직접 클로저 처리를 통해 컨트롤러의 작업에 도달합니다. 파이프라인에서 다음 메서드를 살펴보세요.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()); }로그인 후 복사bootstrap/app.php
에 번들로 포함되어 있으므로 서비스 컨테이너에서 구문 분석되는 것을index.php
에서 확인할 수 있습니다.IlluminateContractsHttpKernel
인터페이스의 구현 클래스AppHttpKernel
가 결정되므로 $kernel은 실제로AppHttpKernel
클래스의 객체입니다.Laravel은 Http 커널을 파싱한 후 애플리케이션에 들어가는 요청 객체를 Http 커널의 핸들 메소드에 전달합니다. 핸들 메소드는 애플리케이션으로 유입되는 요청 객체를 처리하고 응답 객체를 반환하는 역할을 합니다.
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)
에서 발생합니다.$destination = function ($request) { $this->app->instance('request', $request); return $this->router->dispatch($request); }; $firstSlice = function ($passable) use ($destination) { return call_user_func($destination, $passable); };로그인 후 복사
//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); }; }; }
mixed array_reduce ( array $array , callable $callback [, Mixed $initial = NULL ] ) array_reduce() 는 배열 array의 각 유닛에 콜백 함수 콜백을 반복적으로 적용하여 배열을 a로 줄입니다. 단일 값.
#🎜🎜#callback ( 혼합 $carry , 혼합 $item )#🎜🎜#carry#🎜🎜#이 반복이 처음인 경우 이 값은 초기 값입니다. 항목은 이 반복의 값을 전달합니다. #🎜🎜##🎜🎜##🎜🎜#getInitialSlice 메소드의 반환 값은 callbakc 함수에 전달된 $carry 매개변수의 초기 값입니다. 이 값은 이제 getInitialSlice와 Http Kernel의 dispatchToRouter를 넣습니다. $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)); } };
$stack = function ($passable) use ($stack, $pipe) { //解析中间件和中间件参数,中间件参数用$parameter代表,无参数时为空数组 $parameters = array_merge([$passable, $stack], $parameters) return $pipe->handle($parameters) };
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, ];
当请求对象进入Http Kernel的dispatchToRouter
方法后,请求对象在被Router dispatch派发给路由时会进行收集路由上应用的中间件和控制器里应用的中间件。
namespace Illuminate\Foundation\Http; class Kernel implements KernelContract { protected function dispatchToRouter() { return function ($request) { $this->app->instance('request', $request); return $this->router->dispatch($request); }; } } namespace Illuminate\Routing; 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 Events\RouteMatched($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对象来传送请求对象通过收集上来的这些中间件然后到达最终的目的地,在那里会执行路由对应的控制器方法生成响应对象,然后响应对象会依次来通过上面应用的所有中间件的后置操作,最终离开应用被发送给客户端。
限于篇幅和为了文章的可读性,收集路由和控制器中间件然后执行路由对应的处理方法的过程我就不在这里详述了,感兴趣的同学可以自己去看Router的源码,本文的目的还是主要为了梳理laravel是如何设计中间件的以及如何执行它们的,希望能对感兴趣的朋友有帮助。
以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!
相关推荐:
위 내용은 라라벨 미들웨어란 무엇입니까? 라라벨 미들웨어(Middleware) 해석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!