laravel中间件源码分析
laravel中间件源码分析
在laravel5.2中,HTTP 中间件为过滤访问你的应用的 HTTP 请求提供了一个方便的机制。在处理逻辑之前,会通过中间件,且只有通过了中间件才会继续执行逻辑代码。它的主要作用就是过滤Http请求(php aritsan
是没有中间件机制的),同时也让系统的层次(Http过滤层)更明确,使用起来也很优雅。但实现中间件的代码却很复杂,接下来就分析下有关中间件的源码(讨论是在laravel5.2上展开的)。
中间件源码
中间件本身分为两种,一种是所有http的,另一种则是针对route的。一个有中间件的请求周期是:Request得先经过Http中间件,才能进行Router,再经过Requset所对应Route的Route中间件, 最后才会进入相应的Controller代码。laravel把请求分为了两种:http和console。不同的请求方式用它自己的Kernel
来驱动Application
。Http请求则是通过
\Illuminate\Foundation\Http\Kernel
类来驱动,它定义了所有的中间件,其父类\Illuminate\Foundation\Http\Kernel::handle
就是对请求进行处理的入口了
Http中间件
跟踪入口handle()
方法,很容易发现该函数(\Illuminate\Foundation\Http\Kernel::sendRequestThroughRouter
):
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());
}
该函数会把Requset分发到Router(通过方法名就知道了), 主要的逻辑则是通过\Illuminate\Routing\Pipeline
完成的, 作用就是让Requset通过Http中间件的检测,然后再到达Router。这里的代码看起来很优雅,但不是很好理解。所以,了解Pipeline
的运行机制就会明白中间件的使用。
Pipeline的运行实现
Pipleline
基类是\Illuminate\Pipeline\Pipeline
,它的执行在then
方法:
public function then(Closure $destination)
{
$firstSlice = $this->getInitialSlice($destination);
$pipes = array_reverse($this->pipes);
return call_user_func(
array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
);
}
了解这段代码执行的意图,必须要知道array_reduce()做了什么。 为了清楚array_reduce
怎么运行的,先把array_reduce
重写一次:
//将数组中的元素,依次执行$func函数,且上一次的$func的返回值作为下一次调用$func的第一个参数输入
function array_reduce_back($arr, callable $func, $firstResult = null)
{
$result = $firstResult;
foreach ($arr as $v) {
$result = $func($result, $v);
}
return $result;
}
所以,源代码中的$func
是getSlice()
,它返回的是一个回调函数:function($passable) use ($stack, $pipe){...}
($stack
和$pipe
被输入的具体值代替),也就是说作为上一次返回结果输入到下一次$func
的第一个参数是上述的回调函数,如此循环,当数组遍历完成,array_reduce
就返回的是一个回调函数,现在关键就是了解这个回调函数是什么样子,又如何执行?为方便讨论,可分析下面的代码:
call_user_func(
array_reduce([1, 2, 3], $this->getSlice(), $firstSlice), $this->passable
);
执行说明:
1.$result_0
是初始化的值 ,为$firstSlice
,即是\Illuminate\Pipeline\Pipeline::getInitialSlice
的返回回调
2.每遍历一个元素,都会执行\Illuminate\Pipeline\Pipeline::getSlice
的回调,同时也会返回一个回调
3.$result
中的具体执行代码都在getSlice()
中
4.最后的array_reduce
返回结果是$result_3
,是一个有多层闭包的回调函数
5.执行的是call_user_func($result_3, $this->passable)
,即执行function($this->passable) use ($result_2, 3){...}
至此已经清楚了then()
是如何运行的了,要继续下去,则需再搞定回调函数到底怎么执行的.现在再跟着sendRequestThroughRouter
中的Pipeline
走,看它是如何执行的。
// 把具体的参数带进来
return (new Pipeline($this->app))
->send($request)
->through(['\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode'])
->then($this->dispatchToRouter());
用上面的所分析的Pipeline
执行过程,很快就会分析出最后执行的是
function($requset) use (\Illuminate\Foundation\Http\Kernel::dispatchToRouter(), '\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode') {
if ($pipe instanceof Closure) {
return call_user_func($pipe, $passable, $stack);
}
// $name和$parameters很容易得到
// $name = '\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode';
// $parameters = [];
list($name, $parameters) = $this->parsePipeString($pipe);
// 执行的就是\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::handle($request, \Illuminate\Foundation\Http\Kernel::dispatchToRouter())
return call_user_func_array([$this->container->make($name), $this->method],
array_merge([$passable, $stack], $parameters));
}
逻辑处理已经到了\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::handle
,其代码是:
public function handle($request, Closure $next)
{
if ($this->app->isDownForMaintenance()) {
throw new HttpException(503);
}
return $next($request);
}
这里,它处理了这个中间件所需要过滤的条件,同时执行了$next($request)
,即\Illuminate\Foundation\Http\Kernel::dispatchToRouter()
, 这样,就把Request转到了Router中,也就完成了Http中间件的所有处理工作,而$next($request)
是每个中间件都不可少的操作,因为在回调中嵌套了回调,就是靠中间件把Request
传递到下一个回调中,也就会解析到下一个中间件,直到最后一个。紧跟上面的已分析的Pipeline
执行过程,讲其补充完整:
6.执行$result_3中的回调,getSlice
实例化中间件,执行其handle
,在中间件处理中执行回调
7.回调中还嵌套回调的,每个中间件中都需有执行回调的代码$next($request)
,才能保证回调中的回调会执行,执行的顺序就是3::handel,2::handel,1::handel,$first
8.最里面一层,一定是传递给then()的参数,then执行的就是最后一步
9.执行的顺序是由数组中的最后一个,向前,到then()的参数,为了使其执行顺序是数组中的第一个到最后一个,再到then()中的参数,then()方法中就做了一个反转array_reverse
Pipeline小结
现在,Pipeline的所有执行流程就都分析完了。实现代码真的很绕,但理解之后编写自定义的中间件应该就很容易了。现在再把Pipeline
的使用翻译成汉语,应该是这样的
// 使用管道,发送$request,使之通过middleware ,再到$func
(new Pipeline($this->app))->send($request)->through($this->middleware)->then($func);
这样的代码不管是从语义上,还是使用上都很优雅,高!确实是高!再回到源码,Requset的流程就通过dispatchToRouter
进入到了Router
Route中间件
在Router中,\Illuminate\Routing\Router::dispatch
就承接了来自Http中间件的Requset, Router把Request分发到了具体的Route,再进行处理,主要代码如下:
public function dispatchToRoute(Request $request)
{
// 找到具体的路由对象,过程略
$route = $this->findRoute($request);
$request->setRouteResolver(function () use ($route) {
return $route;
});
// 执行Request匹配到Route的事件,具体的代码在这里:\Illuminate\Foundation\Providers\FoundationServiceProvider::configureFormRequests
$this->events->fire(new Events\RouteMatched($route, $request));
// 这里就运行路由中间件了
$response = $this->runRouteWithinStack($route, $request);
return $this->prepareResponse($request, $response);
}
protected function runRouteWithinStack(Route $route, Request $request)
{
// 获取该路由上的中间件
// 简单就点可这样写:
// $middleware = App::shouldSkipMiddleware() ? [] : $this->gatherRouteMiddlewares($route);
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddlewares($route);
// 了解Pipeline后,这里就好理解了,应该是通过管道,发送$request,经过$middleware,再到then中的回调
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request,
$route->run($request)
);
});
}
如何获取Route中间件的,就可以跟gatherRouteMiddlewares
,这个代码并不难,很好跟。接下来,Request就到到达至于Controller, Request是如何到达Controller的代码就不难了,这里就不说了
Controller后执行中间件
成功获取Response后,在public/index.php
58行执行了$kernel->terminate($request, $response);
, 也就是在主要逻辑处理完成之后,再执行此代码,它实际上调用是的\Illuminate\Foundation\Http\Kernel::terminate
, 跟进去就很容易发现,它处理了这此请求所涉及到的中间件,并执行了各自的terminate
方法,到这里,中间件的另一个功能就展现出来了,就是主要逻辑完成之后的收尾工作.到这里为止,中间件就完成了它的使命(一个请求也就完成了).
如何使用中间件
在官方文档上讲解的很清楚注册中间件
中间件小结
至此,中间件的实现逻辑与使用就清晰了.从执行的顺序来分,一个在Controller
之前,一个在Controller
之后,所以它一个很重要的作用就是可以让Controller
专注于自己的主要逻辑的职责更明确. 奇怪的是,但前后两种中间件的执行方式却不一样, \Illuminate\Foundation\Http\Kernel::terminate
,中间件的结束却没有使用Pipeline
, 而是直接foreach
.相同的工作却用两种代码来实现.现在看来,中间件本身并不复杂,但它带给了我两个启发,1.层次明确 2,Pipeline
所带来的优雅.
下面是我学习过程中的一些资源
http://laravelacademy.org/post/3088.html
http://www.jianshu.com/p/3c2791a525d0
laravel中间件源码分析的更多相关文章
- laravel框架源码分析(一)自动加载
一.前言 使用php已有好几年,laravel的使用也是有好长时间,但是一直对于框架源码的理解不深,原因很多,归根到底还是php基础不扎实,所以源码看起来也比较吃力.最近有时间,所以开启第5.6遍的框 ...
- laravel 框架源码分析
laravel框架的文档中的例子很多时候不是很明显,所以想要真正的使用好这个框架,我们可以尝试去阅读它源码中的注释(不得不说laravel源码的注释还是很详细的). 我们先来看一下laravel 的文 ...
- Django中间件部分源码分析
中间件源码分析 中间件简介 中间件是一个用来处理Django的请求和响应的框架级别的钩子.它是一个轻量.低级别的插件系统,用于在全局范围内改变Django的输入和输出.每个中间件组件都负责做一些特定的 ...
- 开源分布式数据库中间件MyCat源码分析系列
MyCat是当下很火的开源分布式数据库中间件,特意花费了一些精力研究其实现方式与内部机制,在此针对某些较为重要的源码进行粗浅的分析,希望与感兴趣的朋友交流探讨. 本源码分析系列主要针对代码实现,配置. ...
- 大熊君大话NodeJS之 ------ Connect中间件第二季(源码分析)
一,开篇分析 大家好,大熊君又回来了,今天这篇文章主要是对"Connect"中间件以及相关辅助中间件,做一个源码分析系列,我想上一篇文章大家也看了, 介绍了使用方式及用途,而这篇也 ...
- koa2中间件koa和koa-compose源码分析原理(一)
koa是基于nodejs平台的下一代web开发框架,它是使用generator和promise,koa的中间件是一系列generator函数的对象.当对象被请求过来的时候,会依次经过各个中间件进行处理 ...
- Django-session中间件源码简单分析
Django-session中间件源码简单分析 settings里有关中间件的配置 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddlew ...
- Flask框架(三)—— 请求扩展、中间件、蓝图、session源码分析
Flask框架(三)—— 请求扩展.中间件.蓝图.session源码分析 目录 请求扩展.中间件.蓝图.session源码分析 一.请求扩展 1.before_request 2.after_requ ...
- laravel源码分析-队列Queue
laravel 源码分析具体注释见 https://github.com/FX-Max/source-analysis-laravel 前言 队列 (Queue) 是 laravel 中比较常用的一个 ...
随机推荐
- javascript 算法
前段时间学习算法方面的知识看了一下用C语言写的一些简单的算法自己用js模拟实现一遍现在整理出来和大家分享一下. 河内塔 斐波那契数列 巴斯卡三角形 三色棋 河内之塔(Towers of Hanoi)是 ...
- (重要) html概念之 input:name与id详解
实例: 带有两个文本字段和一个提交按钮的 HTML 表单: <form action="form_action.asp" method="get"> ...
- shopnc数据库 批量修改商品价格
1.商品价格统一上调50 2.商品价格个别上调50 UPDATE `nc_goods` SET `goods_price` = `goods_price` +50 where goods_id!=10 ...
- jquery判断客户端的类型
针对不同客户端下载链接的页面响应样式不一样,更人性点而已 //匹配客户端类型 var isAndroid = navigator.userAgent.toLowerCase().match(/andr ...
- Ice_cream's world I--hdu2120
Ice_cream's world I Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Othe ...
- MYSQL <=>运算符
<=> 与 =
- android---EditText黄色边框
http://liuzhichao.com/p/612.html 自定义android控件EditText边框背景 柳志超博客 » Program » Andriod » 自定义android控件Ed ...
- WPF 动态更改启动窗体startupUri
原文:WPF 动态更改启动窗体startupUri 第一步: 在 App.xaml 里,把 StartupUri=""去掉,改成 Startup="Applicatio ...
- C语言的本质(20)——预处理之二:条件预处理和包含头文件
我们可以通过定义不同的宏来决定编译程序对哪些代码进行处理.条件编译指令将决定那些代码被编译,而哪些是不被编译的.可以根据表达式的值或者某个特定的宏是否被定义来确定编译条件. 条件编译可分为三种情况,按 ...
- JIRA官方:JIRA报表与分析
访问重要的问题 JIRA系统内置的过滤器可以使你快速访问最重要的问题.通过保存和收藏自定义的过滤器,你可以随时了解项目和团队的优先级. 保持团队同步 创建一个过滤器,可以保存你的任何搜索条件.通过分享 ...