在laravel5.2中,Http的主要作用就是过滤Http请求(php aritsan是没有中间件机制的),同时也让系统的层次(Http过滤层)更明确,使用起来也很优雅。但实现中间件的代码却很复杂,下面就来具分析下有关中间件的源码的内容。

中间件源码

中间件本身分为两种,一种是所有http的,另一种则是针对route的。一个有中间件的请求周期是:Request得先经过Http中间件,才能进行Router,再经过Requset所对应Route的Route中间件, 最后才会进入相应的Controller代码。laravel把请求分为了两种:http和console。不同的请求方式用它自己的Kernel来驱动Application。Http请求则是通过

IlluminateFoundationHttpKernel类来驱动,它定义了所有的中间件,其父类IlluminateFoundationHttpKernel::handle就是对请求进行处理的入口了

Http中间件

跟踪入口handle()方法,很容易发现该函数(IlluminateFoundationHttpKernel::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(通过方法名就知道了), 主要的逻辑则是通过IlluminateRoutingPipeline完成的, 作用就是让Requset通过Http中间件的检测,然后再到达Router。这里的代码看起来很优雅,但不是很好理解。所以,了解Pipeline的运行机制就会明白中间件的使用。

Pipeline的运行实现

Pipleline基类是IlluminatePipelinePipeline,它的执行在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 ,即是IlluminatePipelinePipeline::getInitialSlice的返回回调

2.每遍历一个元素,都会执行IlluminatePipelinePipeline::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(['IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode']) ->then($this->dispatchToRouter());

用上面的所分析的Pipeline执行过程,很快就会分析出最后执行的是

function($requset) use (IlluminateFoundationHttpKernel::dispatchToRouter(), 'IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode') {  if ($pipe instanceof Closure) { return call_user_func($pipe, $passable, $stack); }  // $name和$parameters很容易得到 // $name = 'IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode'; // $parameters = []; list($name, $parameters) = $this->parsePipeString($pipe);   // 执行的就是IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode::handle($request, IlluminateFoundationHttpKernel::dispatchToRouter()) return call_user_func_array([$this->container->make($name), $this->method], array_merge([$passable, $stack], $parameters));}

逻辑处理已经到了IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode::handle,其代码是:

public function handle($request, Closure $next){ if ($this->app->isDownForMaintenance()) { throw new HttpException(503); }  return $next($request);}

这里,它处理了这个中间件所需要过滤的条件,同时执行了$next($request),即IlluminateFoundationHttpKernel::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中,IlluminateRoutingRouter::dispatch就承接了来自Http中间件的Requset, Router把Request分发到了具体的Route,再进行处理,主要代码如下:

public function dispatchToRoute(Request $request)

{ // 找到具体的路由对象,过程略 $route = $this->findRoute($request);  $request->setRouteResolver(function () use ($route) { return $route; });

// 执行Request匹配到Route的事件,具体的代码在这里:IlluminateFoundationProvidersFoundationServiceProvider::configureFormRequests

 $this->events->fire(new EventsRouteMatched($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.php58行执行了$kernel->terminate($request, $response);, 也就是在主要逻辑处理完成之后,再执行此代码,它实际上调用是的IlluminateFoundationHttpKernel::terminate, 跟进去就很容易发现,它处理了这此请求所涉及到的中间件,并执行了各自的terminate方法,到这里,中间件的另一个功能就展现出来了,就是主要逻辑完成之后的收尾工作.到这里为止,中间件就完成了它的使命(一个请求也就完成了)

如何使用中间件

在官方文档上讲解的很清楚注册中间

中间件小结

至此,中间件的实现逻辑与使用就清晰了.从执行的顺序来分,一个在Controller之前,一个在Controller之后,所以它一个很重要的作用就是可以让Controller专注于自己的主要逻辑的职责更明确. 奇怪的是,但前后两种中间件的执行方式却不一样, IlluminateFoundationHttpKernel::terminate,中间件的结束却没有使用Pipeline, 而是直接foreach.相同的工作却用两种代码来实现.现在看来,中间件本身并不复杂,但它带给了我两个启发,1.层次明确 2,Pipeline所带来的优雅.

解析多层list_基于laravel5.2进行中间件源码的解析相关推荐

  1. Jdk1.8 JUC源码增量解析(2)-atomic-LongAdder和LongAccumulator

    转载自 Jdk1.8 JUC源码增量解析(2)-atomic-LongAdder和LongAccumulator 功能简介: LongAdder是jdk1.8提供的累加器,基于Striped64实现. ...

  2. android 生成泛型对象,java android解析多层含有泛型对象的json数据获取不到泛型类型解析失败解决办法...

    ####问题描述 * java 解析多层含有泛型对象的json数据获取不到泛型类型 * 如果将泛型改成实际的类型就能正常解析 * 如果不改成实际的类型泛型数据被解析成com.google.gson.i ...

  3. Spring源码深度解析(郝佳)-学习-源码解析-基于注解切面解析(一)

    我们知道,使用面积对象编程(OOP) 有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共的行为时,例如日志,安全检测等,我们只有在每个对象引用公共的行为,这样程序中能产生大量的重复代码,程序就 ...

  4. Spring源码深度解析(郝佳)-学习-源码解析-基于注解注入(二)

    在Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean解析(一)博客中,己经对有注解的类进行了解析,得到了BeanDefinition,但是我们看到属性并没有封装到BeanDefinit ...

  5. Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean定义(一)

    我们在之前的博客 Spring源码深度解析(郝佳)-学习-ASM 类字节码解析 简单的对字节码结构进行了分析,今天我们站在前面的基础上对Spring中类注解的读取,并创建BeanDefinition做 ...

  6. 从源码角度解析Android中APK安装过程

    从源码角度解析Android中APK的安装过程 1. Android中APK简介 Android应用Apk的安装有如下四种方式: 1.1 系统应用安装 没有安装界面,在开机时自动完成 1.2 网络下载 ...

  7. Go netpoll I/O 多路复用构建原生网络模型之源码深度解析

    原文 Go netpoll I/O 多路复用构建原生网络模型之源码深度解析 导言 Go 基于 I/O multiplexing 和 goroutine 构建了一个简洁而高性能的原生网络模型(基于 Go ...

  8. Java LockSupport以及park、unpark方法源码深度解析

    介绍了JUC中的LockSupport阻塞工具以及park.unpark方法的底层原理,从Java层面深入至JVM层面. 文章目录 1 LockSupport的概述 2 LockSupport的特征和 ...

  9. Java Executor源码解析(3)—ThreadPoolExecutor线程池execute核心方法源码【一万字】

    基于JDK1.8详细介绍了ThreadPoolExecutor线程池的execute方法源码! 上一篇文章中,我们介绍了:Java Executor源码解析(2)-ThreadPoolExecutor ...

最新文章

  1. QTP的那些事---页面弹出框的处理,页面等待加载的处理
  2. jquery jcrop java_jcrop基本参数一览
  3. flex布局一行三个_CSS Flex布局
  4. 原平 计算机培训,原平编程培训,原平编程培训班,原平编程培训完找什么工作 - IT教育频道...
  5. [原]FreeSWITCH uuid_transfer both转移失败(三方通话),如何解决?
  6. linux uniq命令_如何在Linux上使用uniq命令
  7. 第一百五十一期:最新计算机技能需求排名出炉:Python仅排第三,第一你猜得到吗?
  8. python第八周小测验_Python语言程序设计第2周测验+练习题复盘
  9. Spring-Data-Jpa简介
  10. php如何增加字段,php如何增加字段
  11. visual studio怎么编译python_我的计算机上的Python使用哪个版本的Visual Studio进行编译?...
  12. 局域网屏幕共享_我把手机、平板、笔记本,变成了电脑的第二屏幕。
  13. 华中科技大学计算机学院任思浩,华中科技大学2018年本科特优生名单
  14. php函数收集参数,now-go时间百宝箱
  15. Summary:Fater Rcnn
  16. 高数 04.03分部积分法
  17. 电脑中的耳机插进去没有反应
  18. 湖北物联网产业标准联盟成立
  19. 白杨SEO:做个世界杯公众号怎么样?以2022年卡塔尔世界杯来做微信搜一搜的SEO流量实战举例
  20. ajax自动加载blogjava和博客园的rss

热门文章

  1. UVA11764 Jumping Mario【Ad Hoc】
  2. POJ NOI0105-30 含k个3的数【数制】
  3. B00006 函数itoa()
  4. 深度学习学界业界进展调研
  5. 重构代码 —— 函数即变量(Replace temp with Query)
  6. 【matlab】安装 webcam 支持
  7. 从二叉树到完全二叉树
  8. 极简代码(一)—— 精确率和错误率的计算
  9. [面试] 算法(八)—— 树
  10. word的使用(二)