Laravel 源码分析---使用 Pipeline 实现中间件功能

标签: laravel 源码分析 Pipeline 中间件


在我们了解了 Pipeline 的源码及工作过程后(见文章Laravel 源码分析---Pineline),我们来看一下框架中使用 Pipeline 实现中间件功能的代码。

框架中间件使用概述。

在 laravel 框架中中间件的配置主要有两个地方,一个是在 App\Http\Kernel 类中进行配置,一个是在路由设置的时候进行配置。所以 laravel 框架中有两处管理与使用中间件的地方,一个是由 Illuminate\Foundation\Http\Kernel 类进行管理,在类中 sendRequestThroughRouter 方法使用;另一个是在 Illuminate\Routing\Router 类中进行管理,由 runRouteWithinStack 方法使用。接下来我们分别看这两处和中间件的管理与使用相关的代码。

Kernel 类对中间件的管理与使用

在使用 laravel 框架的过程中,我们会在 App\Http\Kernel 类中配置我们全局生效的中间件,我们先来看一下框架在此处的默认配置代码

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     * 每个请求都会经过的中间件,全局生效的中间件
     * @var array
     */
    protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
    ];
    
    /**
     * The application's route middleware groups.
     * 应用中路由的中间件组
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            'throttle:60,1',
            'bindings',
        ],
    ];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     * 路由中可能会用到的中间件的简化别名
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    ];
}

我们看到这个类继承自 Illuminate\Foundation\Http\Kernel 类,主要进行中间件的配置。而其父类主要负责中间件的管理与使用,我们来看其相应源码。

namespace Illuminate\Foundation\Http;

use Illuminate\Routing\Router;
use Illuminate\Routing\Pipeline;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Http\Kernel as KernelContract;

class Kernel implements KernelContract
{
    /**
     * The application implementation.
     *
     * @var \Illuminate\Contracts\Foundation\Application
     */
    protected $app;

    /**
     * The router instance.
     * 框架路由类的实例,管理框架的中所有配置的路由,实现供框架使用的路由功能
     * @var \Illuminate\Routing\Router
     */
    protected $router;

    /**
     * The application's middleware stack.
     * 应用全局生效的中间件队列
     * @var array
     */
    protected $middleware = [];

    /**
     * The application's route middleware groups.
     * 应用的路由中间件组
     * @var array
     */
    protected $middlewareGroups = [];

    /**
     * The application's route middleware.
     * 路由中可能会用到的中间件的简化别名
     * @var array
     */
    protected $routeMiddleware = [];

    /**
     * The priority-sorted list of middleware.
     * Forces the listed middleware to always be in the given order. 
     * 中间件的优先级,强制下面这些中间件按照给定的顺序执行
     * @var array
     */
    protected $middlewarePriority = [
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \Illuminate\Auth\Middleware\Authenticate::class,
        \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \Illuminate\Auth\Middleware\Authorize::class,
    ];

    /**
     * Create a new HTTP kernel instance.
     * 创建一个 HTTP kernel 类的实例
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @param  \Illuminate\Routing\Router  $router
     * @return void
     */
    public function __construct(Application $app, Router $router)
    {
        $this->app = $app;
        $this->router = $router;
        
        //给路由设置某些中间件的优先级
        $router->middlewarePriority = $this->middlewarePriority;

        //设置路由可能会用到的中间件组
        foreach ($this->middlewareGroups as $key => $middleware) {
            $router->middlewareGroup($key, $middleware);
        }
        
        //设置路由可能会用到的中间件及对应的简化别名
        foreach ($this->routeMiddleware as $key => $middleware) {
            $router->middleware($key, $middleware);
        }
    }

    /**
     * Handle an incoming HTTP request.
     * 执行一个 http 请求
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function handle($request)
    {
        try {
            ...
            //将请求通过中间件分发给路由
            $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']->fire('kernel.handled', [$request, $response]);

        return $response;
    }

    /**
     * Send the given request through the middleware / router.
     * 发送 $request,通过全局中间件,并分发给路由 
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    protected function sendRequestThroughRouter($request)
    {
        ...
        //根绝全局配置的中间件,设置中间件任务队列并执行
        //$this->app->shouldSkipMiddleware() 是否跳过中间件
        //$this->dispatchToRouter() 将请求分发给路由
        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }
    
    /**
     * Get the route dispatcher callback.
     * 返回路由分发器的回调函数
     * @return \Closure
     */
    protected function dispatchToRouter()
    {
        return function ($request) {
            $this->app->instance('request', $request);
            
            //根据路由的配置分发请求,
            //并返回相应路由配置的控制器和方法执行后的响应
            return $this->router->dispatch($request);
        };
    }
    
}

我们看到,Kernel 主要是配置全局中间件和路由中间件,管理和执行全局中间件。当请求通过全局中间件后,将请求分发到路由。接下来我们来看路由对请求的分发,以及其对路由中间件的管理和执行。

Router 类对路由中间件的管理与使用

在 laravel 框架中,路由系统是其非常重要的功能之一,路由中间件的配置和是由路由系统中的 Illuminate\Routing\Router 来管理的,其也是框架路由系统的门面类,向外提供路由系统所需要提供的方法。我们来看一下 Router 类下和路由管理与中间件管理相关的功能。

namespace Illuminate\Routing;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
use Illuminate\Contracts\Routing\Registrar as RegistrarContract;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;

class Router implements RegistrarContract
{

    /**
     * The IoC container instance.
     * Ioc 容器实例
     * @var \Illuminate\Container\Container
     */
    protected $container;

    /**
     * The route collection instance.
     * 用户配置的所有路由的集合
     * @var \Illuminate\Routing\RouteCollection
     */
    protected $routes;

    /**
     * The currently dispatched route instance.
     * 当前请求所匹配的路由实例
     * @var \Illuminate\Routing\Route
     */
    protected $current;

    /**
     * The request currently being dispatched.
     * 传入的被分发的请求
     * @var \Illuminate\Http\Request
     */
    protected $currentRequest;

    /**
     * All of the short-hand keys for middlewares.
     * 路由系统中存在简写别名的中间件
     * @var array
     */
    protected $middleware = [];

    /**
     * All of the middleware groups.
     * 路由系统所有会用到的中间件组
     * @var array
     */
    protected $middlewareGroups = [];

    /**
     * The priority-sorted list of middleware.
     * Forces the listed middleware to always be in the given order.
     * 中间件的优先级,强制下面这些中间件按照给定的顺序执行
     * @var array
     */
    public $middlewarePriority = [];

    
    /**
     * 
     * Dispatch the request to the application.
     * 分发请求到某个配置的路由
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function dispatch(Request $request)
    {
        $this->currentRequest = $request;

        return $this->dispatchToRoute($request);
    }

    /**
     * Dispatch the request to a route and return the response.
     * 分发请求到某个路由,并返回执行请求得到的响应
     * @param  \Illuminate\Http\Request  $request
     * @return mixed
     */
    public function dispatchToRoute(Request $request)
    {
        //首先,在用户配置的路由组里面找到匹配请求的路由
        $route = $this->findRoute($request);
        
        //在 $request 设置路由 resolver 
        //以便路由上的中间件拥有访问此路由实例的接口
        $request->setRouteResolver(function () use ($route) {
            return $route;
        });

        ...
        
        //运行路由的中间件,让 $request 通过 $route 配置的中间件
        $response = $this->runRouteWithinStack($route, $request);

        //根据 $request 和 $response,准备 $response 响应对象
        return $this->prepareResponse($request, $response);
    }

    /**
     * 运行路由的中间件,让 $request 通过 $route 配置的中间件
     * @param  \Illuminate\Routing\Route  $route
     * @param  \Illuminate\Http\Request  $request
     * @return mixed
     */
    protected function runRouteWithinStack(Route $route, Request $request)
    {
        //是否应该跳过中间件
        $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
                                $this->container->make('middleware.disable') === true;
    
        $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
        
        //让请求 $request 通过 $route 的中间件,
        //并最终返回路由执行请求的响应
        return (new Pipeline($this->container))
                        ->send($request)
                        ->through($middleware)
                        ->then(function ($request) use ($route) {
                            return $this->prepareResponse(
                                $request, $route->run($request)
                            );
                        });
    }

    /**
     * Gather the middleware for the given route.
     * 获取解析过的路由的中间件
     * @param  \Illuminate\Routing\Route  $route
     * @return array
     */
    public function gatherRouteMiddleware(Route $route)
    {   
        //获取路由的中间件并解析
        $middleware = collect($route->gatherMiddleware())->map(function ($name) {
            return (array) $this->resolveMiddlewareClassName($name);
        })->flatten();
        
        //对中间件按照指定优先级排序
        return $this->sortMiddleware($middleware);
    }

    /**
     * Resolve the middleware name to a class name(s) preserving passed parameters.
     * 根据 $name 解析出来中间件的信息(保留传入中间件的参数)
     * $name 可能为匿名函数(直接返回)
     * $name 为 $this->middleware 中某个中间件名字
     * $name 为 $this->middlewareGroups 中的某个中间件组名字
     * $name 为普通字符串
     * @param  string  $name
     * @return string|array
     */
    public function resolveMiddlewareClassName($name)
    {
        $map = $this->middleware;

        if ($name instanceof Closure) {
            return $name;
        } elseif (isset($map[$name]) && $map[$name] instanceof Closure) {
            return $map[$name];
            
        } elseif (isset($this->middlewareGroups[$name])) {
                //解析中间件组
                return $this->parseMiddlewareGroup($name);
        } else {
            // $name 为字符串,解析字符串中的中间件信息 
            list($name, $parameters) = array_pad(explode(':', $name, 2), 2, null);

            return (isset($map[$name]) ? $map[$name] : $name).
                   (! is_null($parameters) ? ':'.$parameters : '');
        }
    }

    /**
     * Parse the middleware group and format it for usage.
     * 根据中间组信息,解析其中的中间件
     * @param  string  $name
     * @return array
     */
    protected function parseMiddlewareGroup($name)
    {
        $results = [];

        foreach ($this->middlewareGroups[$name] as $middleware) {           
            //递归解析中间件组
            if (isset($this->middlewareGroups[$middleware])) {
                $results = array_merge(
                    $results, $this->parseMiddlewareGroup($middleware)
                );

                continue;
            }

            list($middleware, $parameters) = array_pad(
                explode(':', $middleware, 2), 2, null
            );

            if (isset($this->middleware[$middleware])) {
                $middleware = $this->middleware[$middleware];
            }

            $results[] = $middleware.($parameters ? ':'.$parameters : '');
        }

        return $results;
    }

    /**
     * Sort the given middleware by priority.
     * 按照指定优先级对中间件进行排序
     * @param  \Illuminate\Support\Collection  $middlewares
     * @return array
     */
    protected function sortMiddleware(Collection $middlewares)
    {
        return (new SortedMiddleware($this->middlewarePriority, $middlewares))->all();
    }

    /**
     * Find the route matching a given request.
     * 在用户配置的路由集合($this->routes)中匹配符合请求 $request 的路由
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Routing\Route
     */
    protected function findRoute($request)
    {
        $this->current = $route = $this->routes->match($request);

        $this->container->instance('Illuminate\Routing\Route', $route);

        return $route;
    }
    
    /**
     * Register a short-hand name for a middleware.
     * 为中间件 $class 注册一个简写的名字
     * @param  string  $name
     * @param  string  $class
     * @return $this
     */
    public function middleware($name, $class)
    {
        $this->middleware[$name] = $class;

        return $this;
    }

    /**
     * Register a group of middleware.
     * 注册一个中间件组
     * @param  string  $name
     * @param  array  $middleware
     * @return $this
     */
    public function middlewareGroup($name, array $middleware)
    {
        $this->middlewareGroups[$name] = $middleware;

        return $this;
    }
}

我们看到关于请求 $request 的分发任务进来后(Kernel 类中 $this->router->dispatch($request) 方法的调用),Router 类首先在配置的路由集合中找到匹配 $request 的路由,并解析出匹配到的路由的中间件,让请求 $request 通过中间件,最终返回路由执行请求的响应。

总结

Pipeline 和 Pipeline 在中间件中的应用是 laravel 框架中很重要的一块功能,理解这部分代码的实现,对我们理解框架的设计思想具有很重要的作用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,306评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,657评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,928评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,688评论 0 220
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,105评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,024评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,159评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,937评论 0 212
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,689评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,851评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,325评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,651评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,364评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,192评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,985评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,154评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,955评论 2 279

推荐阅读更多精彩内容

  • 先说几句废话,调和气氛。事情的起由来自客户需求频繁变更,伟大的师傅决定横刀立马的改革使用新的框架(created ...
    wsdadan阅读 3,008评论 0 12
  • Laravel 学习交流 QQ 群:375462817 本文档前言Laravel 文档写的很好,只是新手看起来会有...
    Leonzai阅读 7,223评论 2 12
  • 简介 Laravel 中间件提供了一种方便的机制来过滤进入应用的 HTTP 请求, 如ValidatePostSi...
    godruoyi阅读 901评论 0 6
  • 原文链接 必备品 文档:Documentation API:API Reference 视频:Laracasts ...
    layjoy阅读 8,523评论 0 121
  • 过去做事情急,什么东西拿起来就用,不喜欢进行系统性的学习,造成在使用过程中的错误和低效,现在感觉自己耐心多了,用之...
    马文Marvin阅读 1,919评论 0 10