首页 > 文章详情 > Laravel 底层是如何处理 HTTP 请求的?

Laravel 底层是如何处理 HTTP 请求的?

原创 YuanDong 2019-11-28 浏览量(101)

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

$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 接口及其实现类到容器中:

$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():

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

回到 Kernel

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

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() 方法运用应用的启动类:

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 请求处理了:

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() 做一些善后清理工作,并最终退出脚本。这些善后清理工作主要包括运行终止中间件,以及注册到服务容器的一些终止回调:

public function terminate($request, $response)
{
$this->terminateMiddleware($request, $response);

$this->app->terminate();
}
总结:Laravel生命周期

1、Laravel 采用了单一入口模式,应用的所有请求入口都是 public/index.php 文件。

2、注册类文件自动加载器 : Laravel通过 composer 进行依赖管理,无需开发者手动导入各种类文件,而由自动加载器自行导入。

3、创建服务容器:从 bootstrap/app.php 文件中取得 Laravel 应用实例 $app (服务容器)
创建 HTTP / Console 内核:传入的请求会被发送给 HTTP 内核或者 console 内核进行处理

4、载入服务提供者至容器:内核启动过程中最重要的动作之一就是为应用载入服务提供者,应用的所有服务提供者都被配置在 config/app.php 配置文件的 providers 数组中。首先,所有提供者的 register 方法被调用,然后,所有提供者被注册之后,boot 方法被调用。服务提供者负责引导启动框架的全部各种组件,例如数据库、队列、验证器以及路由组件。

5、分发请求:一旦应用完成引导和所有服务提供者都注册完成,Request 将会移交给路由进行分发。路由将分发请求给一个路由或控制器,同时运行路由指定的中间件

热门评论 (0)

网友评论 0 条评论 / 0 人参与