在laravel中,middleware是用管道实现的,要理解middleware,首先要了解Pipeline。
下文会对代码做一定简化方便理解
Pipeline 管道
以全局中间件为例,首先在kernel中定义middleware数组
protected $middleware = [
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
];
核心代码
protected function sendRequestThroughRouter($request)
{
// something...
return (new Pipeline($this->app)) // 实例化Pipeline
->send($request) //带上请求实例
->through($this->middleware) //穿过中间件
->then($this->dispatchToRouter()); //将请求派发到路由地址中
}
send()
通过管道处理的对象实例 这次为$request
public function send($passable) {
$this->passable = $passable;
return $this;
}
through()
要通过的管道 middleware
public function through($pipes) {
$this->pipes = is_array($pipes) ? $pipes : func_get_args();
return $this;
}
then()
请求到底是如何通过middleware,就是这个方法实现
public function then(Closure $destination) {
$pipeline = array_reduce(
array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
方法中,最关键的就是函数array_reduce,此函数参数如下
array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] ) : mixed
函数将对数组 array 每一个元素使用 回调函数callback 进行迭代,initial为迭代时的初始值
在此场景中,就是carry()函数迭代所有middleware数组中的元素,以闭包函数为初始值
再观察carry()函数
protected function carry() {
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
//实例化
$pipe = $this->getContainer()->make($pipe);
//$this->method 就是handle
$carry = $pipe->{$this->method}(...$parameters);
$parameters = array_merge([$passable, $stack], $parameters);
return $carry;
};
};
}
将上次middleware中handle执行的闭包,传入下个middleware中,直至结束
如果只注册上面四个个全局middleware的话,then()方法大概执行逻辑如下
TrustProxies ValidatePostSize ValidatePostSize HandleCors
public function then1(Closure $destination){
return function ()use ($destination){
return (new ValidatePostSize)->handle(...function ()use ($destination){
return (new CheckForMaintenanceMode)->handle(...function ()use ($destination){
return (new HandleCors)->handle(...function ()use ($destination){
return (new TrustProxies )->handle(...function ()use ($destination){
return $this->prepareDestination($destination);
});
});
});
});
};
}
所以也解释了为什么要用array_reverse 倒排数组,先进后出,倒排一下保证是定义数组的时候的顺序
Middlerware 中间件
中间件,顾名思义,就是放在中间的物品。
偷个很形象的图
在app前后可分前置后置,在内层外层可分全局部分,甚至可以匹配路由。
在使用时,可以使用中间件做一些通用的处理
以全局前置中间件CheckForMaintenanceMode为例
CheckForMaintenanceMode
字面意思 检查是否为维护模式。由上文,我们只需关系核心方法handle
public function handle($request, Closure $next)
{
//down文件存在即维护模式
if ($this->app->isDownForMaintenance()) {
$data = json_decode(file_get_contents($this->app->storagePath().'/framework/down'), true);
//是否为配置中允许访问的ip
if (isset($data['allowed']) && IpUtils::checkIp($request->ip(), (array) $data['allowed'])) {
return $next($request);
}
//是否为配置中允许访问的路由
if ($this->inExceptArray($request)) {
return $next($request);
}
//真的维护就抛出异常
throw new MaintenanceModeException($data['time'], $data['retry'], $data['message']);
}
//到下一层中间件中验证
return $next($request);
}
laravel也支持自定义middleware,感兴趣的可以去看文档
thinkphp中的middleware
public function pipeline(string $type = 'global')
{
return (new Pipeline())
// 关键函数换成了array_map 不过也是迭代数组+匿名函数
->through(array_map(function ($middleware) {
return function ($request, $next) use ($middleware) {
[$call, $params] = $middleware;
if (is_array($call) && is_string($call[0])) {
//$call[0] 中间件类名 $call[1] handle
//
$call = [$this->app->make($call[0]), $call[1]];
}
$response = call_user_func($call, $request, $next, ...$params);
if (!$response instanceof Response) {
throw new LogicException('The middleware must return Response instance');
}
return $response;
};
}, $this->sortMiddleware($this->queue[$type] ?? [])))
->whenException([$this, 'handleException']);
}
fend中的middleware
public function handle($request)
{
//直接用数组下标操作
if (! isset($this->middlewares[$this->offset]) && ! empty($this->coreHandler)) {
$handler = $this->coreHandler;
} else {
$handler = $this->middlewares[$this->offset];
is_string($handler) && $handler = new $handler();
}
//handle 方法改成 process
if (! method_exists($handler, 'process')) {
throw new FendException(sprintf('Invalid middleware, it have to provide a process() method.'));
}
return $handler->process($request, $this->next());
}
protected function next()
{
$this->offset ++;
return $this;
}
easyswoole
easywoole并没有实现middleware,使用onRequest()和 afterAction()进行控制器的前后操作
try {
//为啥不顺便叫beforeAction呢
if ($this->onRequest($actionName) !== false) {
$forwardPath = $this->$actionName();
}
} catch (\Throwable $throwable) {
$this->onException($throwable);
} finally {
try {
$this->afterAction($actionName);
} catch (\Throwable $throwable) {
$this->onException($throwable);
}