最近在学习laravel框架的时候,发现框架实现了中间件,闲着没事就顺藤摸瓜,研究了其实现原理,有分析不对的请留言指正。
百度百科解读中间件
中间件是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的。目前,它并没有很严格的定义,但是普遍接受IDC的定义:中间件是一种独立的系统软件服务程序,分布式应用软件借助这种软件在不同的技术之间共享资源,中间件位于客户机服务器的操作系统之上,管理计算资源和网络通信。从这个意义上可以用一个等式来表示中间件:中间件=平台+通信,这也就限定了只有用于分布式系统中才能叫中间件,同时也把它与支撑软件和实用软件区分开来
Laravel解读中间件?
中间件为过滤进入应用的 HTTP 请求提供了一套便利的机制。例如,Laravel 内置了一个中间件来验证用户是否经过认证(如登录),如果用户没有经过认证,中间件会将用户重定向到登录页面,而如果用户已经经过认证,中间件就会允许请求继续往前进入下一步操作。
当然,除了认证之外,中间件还可以被用来处理很多其它任务。比如:CORS 中间件可以用于为离开站点的响应添加合适的头(跨域);日志中间件可以记录所有进入站点的请求,从而方便我们构建系统日志系统。
Laravel 框架自带了一些中间件,包括认证、CSRF 保护中间件等等。所有的中间件都位于 app/Http/Middleware 目录下
个人解读
中间件主要是处理行为前的逻辑,比如权限验证,数据过滤,黑名单验证等,可以避免过多的if else语句出现在处理业务逻辑的地方,进行行为验证与业务逻辑处理分离,使得整体的代码结构看上去更加优雅更加清晰。
首先是源码跟踪 (入口文件\project\public\index.php)
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
执行 ( Illuminate\Foundation\Http\Kernel.php)
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
} catch (Throwable $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new RequestHandled($request, $response)
);
return $response;
}
继而执行 sendRequestThroughRouter 方法
所有的请求都是在这进行处理,注意Pipeline对象,类似于Linux里面的管道符|,此类主要进行请求之前过滤的操作,如中间件的调用,可以看出这个方法支持链式调用,通过send方法设置管道发送的对象,
through方法设置是否需要进行中间件操作,而这些中间件的加载通过 path\app\Http\Kernel.php 来进行配置。
/**
* 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());
}
最后通过then方法进行执行,代码如下
这段代码看起来比较简单,深入了解之后发现还是比较绕,表示卡在这里比较久,脑子一直转不过来,可能比较笨,哈哈,当然 在这里花费的时间比较长,之后在我会文本末端写一个例子来说明此处方法调用逻辑
//$this->pipes() 是一个数组 代码如下
protected $pipes = [
\App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* Run the pipeline with a final destination callback.
*
* @param \Closure $destination
* @return mixed
*/
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
//carry 方法 去掉没必要的代码,以免影响阅读和理解
public function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
try {
//$this->method="handle"
$parameters = [$passable, $stack];
return method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);;
} catch (Throwable $e) {
return $this->handleException($passable, $e);
}
};
};
}
//最后执行的方法体,是一个闭包(不了解闭包的同学,了解后才能继续阅读哦),不然都不知道这是干嘛用的,
public function prepareDestination($destination)
{
return function ($passable) use ($destination) {
try {
return $destination($passable);
} catch (Throwable $e) {
return $this->handleException($passable, $e);
}
};
}
再次解读then方法
$pipeline = array_reduce(
array_reverse($this->pipes()),
$this->carry(),
$this->prepareDestination($destination)
);
return $pipeline($this->passable);
这里会生成一个循环堆调用,结构如下所示
为保证每个方法都能调用到必须以如下方式返回,否则只会调用到第一个方法的时候直接结束,导致请求中断,结果会直接返回。
public function handle(Request $request, $next)
{
//@todo 业务逻辑处理
//$request 请求体 $next下一个处理的方法逻辑,也就上图所画的调用逻辑
return $next($request);
}
到这里 整个中间件的实现原理已分析完毕,主要还是掌握闭包堆栈处理,这也是laravel处理中间件的主要逻辑方法,理解起来还是比较麻烦的,在这里我给大家准备了个例子,方便打印调试(laravel中调试简直就是一场噩梦,浏览器直接奔溃)。
例子(复制即用)
class handdle{
public function go($a,$b)
{
echo "我进来了哦go".PHP_EOL;
return $b($a); //如果这里不这样调用,程序就到此结束
}
}
class handdleQuestion{
public function go($a,$b)
{
echo "我进来了哦handdleQuestion".PHP_EOL;
return $b($a); //如果这里不这样调用,程序就到此结束
return true;
}
}
function test(){
return function ($stack, $pipe) {
// echo "return ".$pipe.PHP_EOL;
return function ($passable) use ($stack, $pipe) {
// print_r($stack);die;
$parameters = [$passable, $stack];
return $pipe->go(...$parameters);
};
};
}
function call_back(Closure $destination){
return function ($passable) use ($destination) {
return $destination($passable);
};
}
$obj = function($a){ //这是最后执行方法
echo "哈哈哈".rand().PHP_EOL;
var_dump($a); //不出意外这里会打印$passable对象
};
$pipeline = array_reduce(
[new handdle(),new handdleQuestion()], test(), call_back($obj)
);
$passable = new stdClass();
$passable->id = 100;
$pipeline($passable);