Laravel源码剖析之请求的处理上(四)

上篇讲了make方法-->Laravel源码剖析之make详解(三)_Attitude_do_it的博客-CSDN博客,

根据make方法的分析可以得出:

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

此处的$kernel是App\Http\Kernel的实例,如果还不懂的小伙伴,可以私信我。

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

$response->send();

这段代码,是Laravel最核心的请求处理部分,既然$kernel是App\Http\Kernel的实例,此处调用了它的handle方法,那么我们来看看这个类和这个函数都做了什么:

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     * 应用全局的HTTP中间件
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        \App\Http\Middleware\TrustProxies::class,
        \App\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::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\Session\Middleware\AuthenticateSession::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' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
    ];

    /**
     * The priority-sorted list of middleware.
     * 按照优先级排序的中间件
     * This forces non-global middleware to always be in the given order.
     *
     * @var array
     */
    protected $middlewarePriority = [
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\Authenticate::class,
        \Illuminate\Routing\Middleware\ThrottleRequests::class,
        \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \Illuminate\Auth\Middleware\Authorize::class,
    ];
}

可以看到此类继承了Illuminate\Foundation\Http\Kernel,并且没有重写它的handle方法,那么我们就要定位到Illuminate\Foundation\Http\Kernel的handle方法:

/**
     * Handle an incoming HTTP request.
     * 处理进来的HTTP请求
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function handle($request)
    {
        try {
            //启用对_method请求参数的支持,enableHttpMethodParameterOverride下方有说明
            $request->enableHttpMethodParameterOverride();

            $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']->dispatch(
            new RequestHandled($request, $response)
        );

        return $response;
    }

    /**
     * Enables support for the _method request parameter to determine the intended HTTP method.
     *
     * Be warned that enabling this feature might lead to CSRF issues in your code.
     * Check that you are using CSRF tokens when required.
     * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered
     * and used to send a "PUT" or "DELETE" request via the _method request parameter.
     * If these methods are not protected against CSRF, this presents a possible vulnerability.
     *
     * The HTTP method can only be overridden when the real HTTP method is POST.
     * 启用对_method请求参数的支持,以确定预期的HTTP方法。请注意,启用此功能可能会导致代码中出现 
       CSRF问题。检查您是否在需要时使用CSRF令牌。如果启用了HTTP方法参数覆盖,则可以更改带有方法 
       “POST”的html表单,并使用该表单通过_methodrequest参数发送“PUT”或“DELETE”请求。如果这些 
       方法没有针对CSRF进行保护,则可能存在漏洞。只有当真正的HTTP方法是POST时,才能重写HTTP方法
     */
    public static function enableHttpMethodParameterOverride()
    {
        self::$httpMethodParameterOverride = true;
    }

    /**
     * Send the given request through the middleware / router.
     * 通过中间件/路由器发送给定的请求
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    protected function sendRequestThroughRouter($request)
    {   
        // 绑定request实例到容器
        $this->app->instance('request', $request);
        //清除之前已经解析过的request实例
        Facade::clearResolvedInstance('request');
        
        $this->bootstrap();
        //处理请求的关键部分
        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }

看看Pipeline类都干了什么:

    /**
     * Create a new class instance.
     * 创建新的类实例
     * @param  \Illuminate\Contracts\Container\Container|null  $container
     * @return void
     */
    public function __construct(Container $container = null)
    {
        $this->container = $container;
    }
    /**
     * Set the object being sent through the pipeline.
     * 设置通过管道发送的对象
     * @param  mixed  $passable
     * @return $this
     */
    public function send($passable)
    {
        $this->passable = $passable;

        return $this;
    }
   /**
     * Set the array of pipes.
     *
     * @param  array|mixed  $pipes
     * @return $this
     */
    public function through($pipes)
    {   
        //这里其实是设置需要的中间件
        $this->pipes = is_array($pipes) ? $pipes : func_get_args();

        return $this;
    }
   /**
     * Run the pipeline with a final destination callback.
     * 为最终目标回调运行pipeline
     * @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);
    }

then方法中用到了array_reverse和array_reduce,这里不对这两个方法进行讲解,希望小伙伴可以自己动手去查阅,这样印象更深刻一点。这里主要讲解一些carry方法:

    /**
     * Get a Closure that represents a slice of the application onion.
     * 获取表示应用程序片段的闭包
     * @return \Closure
     */
    protected function carry()
    {
        //$stack是刚刚then方法中$this->prepareDestination($destination)的返回值
        //$pipe是array_reverse($this->pipes())的元素,也就是刚刚through方法中设置的中间件,
           元素值都是中间件的类名字符串,知道了这些下边就容易理解了
        return function ($stack, $pipe) {
            return function ($passable) use ($stack, $pipe) {
                try {
                 
                    if (is_callable($pipe)) {
                           // 既然知道$pipe是字符串,那这里指定是false
                        // If the pipe is a callable, then we will call it directly, but otherwise we
                        // will resolve the pipes out of the dependency container and call it with
                        // the appropriate method and arguments, returning the results back out.
                        return $pipe($passable, $stack);
                    } elseif (! is_object($pipe)) {
                        //会进入到这个方法,parsePipeString方法下边有介绍
                        [$name, $parameters] = $this->parsePipeString($pipe);

                        // If the pipe is a string we will parse the string and resolve the class out
                        // of the dependency injection container. We can then build a callable and
                        // execute the pipe function giving in the parameters that are required.
                        //这里会从容器中拿到pipe即中间件的实例
                        $pipe = $this->getContainer()->make($name);
                        
                        $parameters = array_merge([$passable, $stack], $parameters);
                    } else {
                        // If the pipe is already an object we'll just make a callable and pass it to
                        // the pipe as-is. There is no need to do any extra parsing and formatting
                        // since the object we're given was already a fully instantiated object.
                        $parameters = [$passable, $stack];
                    }
                    //这里会调用中间件的handle方法
                    $carry = method_exists($pipe, $this->method)
                                    ? $pipe->{$this->method}(...$parameters)
                                    : $pipe(...$parameters);

                    //返回中间件的处理结果
                    return $this->handleCarry($carry);
                } catch (Exception $e) {
                    return $this->handleException($passable, $e);
                } catch (Throwable $e) {
                    return $this->handleException($passable, new FatalThrowableError($e));
                }
            };
        };
    }

    /**
     * Parse full pipe string to get name and parameters.
     * 解析完整pipe字符串以获取名称和参数
     * @param  string  $pipe
     * @return array
     */
    protected function parsePipeString($pipe)
    {
        [$name, $parameters] = array_pad(explode(':', $pipe, 2), 2, []);

        if (is_string($parameters)) {
            $parameters = explode(',', $parameters);
        }

        return [$name, $parameters];
    }

从carry方法可以看出,在真正掉处理请求前,先执行了中间件的方法。

到这里框架对于分配路由前的处理就算是正式完成了,下篇来分析一下框架分配路由的具体操作,也就是针对下边这段代码中then方法的参数$this->dispatchToRouter()的解读,看看它里边到底做了哪些事情。有兴趣的小伙伴可以点下关注,咱们一起探讨,一起进步,欢迎私信沟通

return (new Pipeline($this->app))
            ->send($request)
            ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
            ->then($this->dispatchToRouter());

猜你喜欢

转载自blog.csdn.net/Attitude_do_it/article/details/121535589