一次Laravel请求的生命周期

思考并回答以下问题:

生命周期概览

第一件事

Laravel应用的所有请求入口都是public/index.php文件,所有请求都会被web服务器(Apache/Nginx)导向这个文件。index.php文件包含的代码并不多,但是,这里是加载框架其它部分的起点。

index.php文件载入Composer生成的自动加载设置,然后从bootstrap/app.php脚本获取Laravel应用实例,Laravel的第一个动作就是创建服务容器实例。

HTTP/Console内核

接下来,请求被发送到HTTP内核或Console内核(分别用于处理Web请求和Artisan命令),这取决于进入应用的请求类型。这两个内核是所有请求都要经过的中央处理器,现在,就让我们聚焦在位于app/Http/Kernel.php的HTTP内核。

HTTP内核继承自Illuminate\Foundation\Http\Kernel类,该类定义了一个bootstrappers数组,这个数组中的类在请求被执行前运行,这些bootstrappers配置了错误处理、日志、检测应用环境以及其它在请求被处理前需要执行的任务。

HTTP内核还定义了一系列所有请求在处理前需要经过的HTTP中间件,这些中间件处理HTTP会话的读写、判断应用是否处于维护模式、验证CSRF令牌等等。

HTTP内核的handle方法签名相当简单:获取一个Request,返回一个Response,可以把该内核想象作一个代表整个应用的大黑盒子,输入HTTP请求,返回HTTP响应。

服务提供者

内核启动过程中最重要的动作之一就是为应用载入服务提供者,应用的所有服务提供者都被配置在config/app.php配置文件的providers数组中。首先,所有提供者的register方法被调用,然后,所有提供者被注册之后,boot方法被调用。

服务提供者负责启动框架的所有各种各样的组件,比如数据库、队列、验证器,以及路由组件等,正是因为他们启动并配置了框架提供的所有特性,所以服务提供者是整个Laravel启动过程中最重要的部分。

分发请求

一旦应用被启动并且所有的服务提供者被注册,Request将会被交给路由器进行分发,路由器将会分发请求到路由或控制器,同时运行所有路由指定的中间件。

聚焦服务提供者

服务提供者是启动Laravel应用中最关键的部分,应用实例被创建后,服务提供者被注册,请求被交给启动后的应用进行处理,整个过程就是这么简单!

对Laravel应用如何通过服务提供者构建和启动有一个牢固的掌握非常有价值,当然,应用默认的服务提供者存放在app/Providers目录下。

默认情况下,AppServiceProvider是空的,这里是添加自定义启动和服务容器绑定的最佳位置,当然,对大型应用,你可能希望创建多个服务提供者,每一个都有着更加细粒度的启动。

总体流程

Web服务器对HTTP请求的处理流程大体上都是这样的:在某个端口监听请求,请求进入后运行程序,然后将程序运行结果以响应的形式发送出去。

基于Laravel框架构建的Web应用处理HTTP请求的流程也是如此。所有HTTP请求都会被转发到单入口文件public/index.php,处理HTTP请求的核心代码如下(忽略HTTP请求处理之外的代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);

// 绑定处理HTTP请求的接口实现到服务容器
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);

// 从服务容器中解析处理HTTP请求的Kernel实例
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

// 处理HTTP请求的核心代码
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

// 发送响应
$response->send();

// 终止程序,做一些善后及清理工作
$kernel->terminate($request, $response);

在上面这段程序中,首先会创建一个Application实例,作为全局的服务容器,然后将处理请求的核心类Kernel实现实例绑定到该容器中,以便后续通过它处理HTTP请求。我们通过服务器捕获请求并将其传递给Kernel实例进行处理,处理结果是准备好的响应实例,调用该响应实例的send()方法即可将其发送给发起请求的客户端。最后,我们执行Kernel实例上的terminate()终止程序,退出脚本。

以上就是Laravel框架处理HTTP请求的一般流程,所有核心逻辑都位于$kernel->handle()方法调用中。下面我们就来一探究竟。

服务容器

Laravel框架提供了一个功能强大的服务容器,用于管理类之间的依赖关系,关于其底层原理还可以参考学徒到工匠系列中的介绍,这里我们就不深入展开了。服务容器封装了绑定到某个接口的对应实现类的实例化过程,你可以在需要对应实现实例的时候通过接口从容器中获取。

在上面的代码中,$app对应的就是服务容器实例,并且在我们获取到该实例后就注册了Kernel接口及其实现类到容器中:

1
2
3
4
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);

singleton方法会以单例方式在服务容器中将App\Http\Kernel实例绑定到Illuminate\Contracts\Http\Kernel接口,后续我们要获取App\Http\Kernel实例,就可以通过Illuminate\Contracts\Http\Kernel接口从服务容器中获取,获取方法是$app->make():

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

回到Kernel

下面我们就正式进入$kernel->handle()方法内部看看HTTP请求是被如何处理的。打开Illuminate\Foundation\Http\Kernel(App\Http\Kernel的父类),查看handle方法,可以看到核心处理逻辑通过sendRequestThroughRouter方法实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
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());
}

在发送请求到路由之前,先调用bootstrap()方法运用应用的启动类:

1
2
3
4
5
6
7
8
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,
];

这些个启动类在路由解析之前执行,相当于对整个应用进行初始化。通过类名就能窥探出对应的操作意图,分别是加载环境变量和全局配置、配置异常处理逻辑、注册门面和服务提供者(根据config/app.php中的providers和alias配置值)、以及执行所有已注册服务提供者的boot方法,具体的实现逻辑我这里就不一一展开的,你可以自己去看下。

然后就是真正的HTTP请求处理了:

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

Laravel框架以管道模式来处理HTTP请求,首先通过全局中间件对请求进行处理,如果返回false直接退出,不会做路由解析处理。

全局中间件都校验通过才会将请求分发到路由器进行处理,路由器会将请求URL路径与应用注册的所有路由进行匹配,如果有匹配的路由,则先收集该路由所分配的所有路由中间件,通过这些路由中间件对请求进行过滤,所有路由中间件校验通过才会运行对应的匿名函数或控制器方法,执行相应的请求处理逻辑,最后准备好待发送给客户端的响应。

终止Kernel

响应准备就绪后,就会通过$response->send()发送给发起请求的客户端,之后还要运行$kernel->terminate()做一些善后清理工作,并最终退出脚本。这些善后清理工作主要包括运行终止中间件,以及注册到服务容器的一些终止回调:

1
2
3
4
5
6
public function terminate($request, $response)
{
$this->terminateMiddleware($request, $response);

$this->app->terminate();
}
0%