laravel请求响应的过程

本文主要介绍laravel处理请求到响应的详细过程。当在浏览器上输入了一个url之后都发生了什么。

我的服务器部署就是常用LNMP方式,首先看下我的Nginx配置,很简单:


    server {
        listen       8888;

		root /home/work/admin/public;

		location / {
			try_files $uri /index.php?$query_string;
		}

        location = /favicon.ico { access_log off; log_not_found off; }
		location = /robots.txt  { access_log off; log_not_found off; }

		error_page 404 /index.php;

        location ~ \.php$ {
			fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
			fastcgi_param  SCRIPT_FILENAME  /home/work/admin/public/$fastcgi_script_name;
            include        fastcgi_params;
        }
    }

从上面的配置也可以看到,当我访问url的时候,入口文件是index.php,是在/home/work/admin/public下的index.php.它是我们访问服务器的大门。那看一下index.php都发生了什么。

index.php 源码:


require __DIR__.'/../vendor/autoload.php';

$app = require_once __DIR__.'/../bootstrap/app.php';

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

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

$response->send();

$kernel->terminate($request, $response);

看代码,index.php主要干了这几件事:

1.require autoload.php。该语句会帮助我们自动加载所有我们通过composer方法安装的类;

2.创建一个Container实例app.实例化会进行一些重要的初始化,包括绑定服务providers等等。

Container确切的说是Service Container.容器是laravel很吊的一个概念了。它可以理解为一个大管家,我们可以把所有要依赖的类注入到容器中,然后在需要的地方使用它。注入的方法包括binding,singleton,instance,解析的方法就是make或者resovle。

3.绑定HTTP/Kernel,Console/Kernel,ExceptionHandler三个类。他们三个的作用分别是处理HTTP请求,Console命令,处理异常。

4.我们在上面绑定了类,那么下面就开始解析使用:

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

  如果你仔细看,就会发现,我只需要make对应的interface酒醒,具体是哪个实现类,我们在使用时并不关心,而是在上面绑定的时候决定的。如果定义了多个实现类,那么就需要根据名称区分,这点和spring boot的Service接口定义了多个实现类,然后根据@Service(somename)是完全相同的。

容器让你根本不需要再显示地去实例化一个类。

5..构造request对象。

Illuminate\Http\Request::capture()。laravel会从php全局变量获取参数。包括$_SERVER,$_COOKIE,$_FILES,$_GET,$_POST等等。

6.开始调用和HTTP\Kernel的实现类的handle方法。到目前为止,index的迎宾工作就结束了,接下来的任务就交给业务人员来做了。

该类是基类

Illuminate\Foundation\Http;

下面的代码是实例的初始化,以及handle处理方法。

 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->aliasMiddleware($key, $middleware);
        }
    }

    /**
     * Handle an incoming HTTP request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function handle($request)
    {
        try {
            $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 Events\RequestHandled($request, $response)
        );

        return $response;
    }

初始化的作用主要包括加载路由中间件分组等等。

看看处理handle方法都干了什么:

1、最重要的就是这句

    $response = $this->sendRequestThroughRouter($request);

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());
    }

它主要干了以下几件事:

1)  app绑定request;

2)  执行bootstrap方法,这个相当重要了。包括加载环境变量(.env),加载配置(config),修改系统默认异常处理函数。注册Facades,注册Register所有配置的服务provider,最后是执行所有Service providers的boot方法。从这你也看到了。服务提供者的boot方法是再所有register之后才执行的。

其实,就是下面这些类(看名字):

  protected $bootstrappers = [
        \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
        \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
        \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
        \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
        \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
        \Illuminate\Foundation\Bootstrap\BootProviders::class,
    ];

在处理器真正处理一个响应之前,是进行各种初始化,我觉得最重要的应该就是上面的bootstrap了。上面的每一个bootstraper对于laravel来讲都是非常重要的。

3)接下来,管道Pipeline该出场了。请求变身之后开始到时空隧道走一波了,这个管道里面设下层层关卡,必须一关一关通关后才可以迎娶梦中情人儿。

(1)准备工作:把请求对象request发送出去,并告知要通过哪些关卡(通过哪些中间件)。实现的方法就是 send和through方法。

(2)实现递归处理,最终执行处理响应逻辑。这步比较复杂,该步骤执行的方法是then。它的原型:

 public function then(Closure $destination)
    {
        $pipeline = array_reduce(
            array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
        );

        return $pipeline($this->passable);
    }

先概括地说,就是首先请求先经过中间件的处理,然后才轮到最后的响应方法,也就是上述的destination,该参数是一个闭包,说白了就是一个匿名函数,原型如下:

  protected function dispatchToRouter()
    {
        return function ($request) {
            $this->app->instance('request', $request);

            return $this->router->dispatch($request);
        };
    }

我们研究一下管道是怎么实现中间件的处理的。

其实看array_reduce就明白了,对数组进行迭代。此时的数组是一段段管道,管道就是封装的中间件。回调函数carry的原型是:

 protected function carry()
    {
        return function ($stack, $pipe) {
            return function ($passable) use ($stack, $pipe) {
                if (is_callable($pipe)) {
                    // If the pipe is an instance of a Closure, we will just call it directly but
                    // otherwise we'll resolve the pipes out of the container and call it with
                    // the appropriate method and arguments, returning the results back out.
                    return $pipe($passable, $stack);
                } elseif (! is_object($pipe)) {
                    list($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 = $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];
                }

                return method_exists($pipe, $this->method)
                                ? $pipe->{$this->method}(...$parameters)
                                : $pipe(...$parameters);
            };
        };
    }

passables就是传递的对象,这里是request。对于每一个管道,最终都会调用该管道的handle方法,也就是每个中间件的方法。每个分片处理完了都会返回一个处理后的response。

中间件处理完之后,就会到达目的方法,就是上面的闭包函数:dispatchToRouter()。

不过我上面只是说了大概,还没有说怎么执行的。为什么在then方法中要用array_reverse,执行顺序是什么样呢?

其实上array_reduce实现了一个层层迭代的闭包,初始值是我们最重要执行的闭包。当我们要执行这个迭代的闭包时,就会从外往里去执行,就像一个洋葱一样,一层一层去拨。这也就是解释了当时为什么要用个array_reverse操作了,中间件的执行是有顺序的,先定义的就要先执行,那么我们就必须做一个倒叙才可以实现这种场景。

有一篇文章对这一块解释的非常好,可参考: laravel的PipeLine reduce

好了,到了最终的闭包dispatchRouter,最终要执行router的dispatch:

 public function dispatch(Request $request)
    {
        $this->currentRequest = $request;

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

处理请求首先要根据request去match对应的路由,路由服务时在app初始化的时候i加载的,然或在bootstrap()方法进行register的。

关于路由这块,比较好的文章: laravel的路由 laravel路由原理

找到目的路由,再匹配对应的action,然后进行响应处理返回响应。

到目前为止,已经返回了处理后的response.

再到index.php,之后服务器会返回处理后的响应。

send执行完毕后,Http Kernel会执行 terminate 方法,该方法调用terminate中间件里的 terminate 方法,从而结束整个应用生命周期。

--------EOF---------
微信分享/微信扫码阅读