美文网首页laravel 源码分析
Laravel 源码分析---使用 Pipeline 实现中间件

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

作者: 上善若水_f6a4 | 来源:发表于2018-05-12 13:05 被阅读0次

    标签: 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 框架中很重要的一块功能,理解这部分代码的实现,对我们理解框架的设计思想具有很重要的作用。

    相关文章

      网友评论

        本文标题:Laravel 源码分析---使用 Pipeline 实现中间件

        本文链接:https://www.haomeiwen.com/subject/yincdftx.html