xiaohuilam / laravel

Laravel 深入详解 —— 源代码解析,新手进阶指南
433 stars 80 forks source link

14. Response 对象和 Kernel::terminate #14

Open xiaohuilam opened 5 years ago

xiaohuilam commented 5 years ago

在前面 01. HTTP 入口解析 的第 54-56 行代码分析时 https://github.com/xiaohuilam/laravel/blob/7028b17ed8bf35ee2f1269c0f9c985b411cb4469/public/index.php#L54-L56

我们只分析了 $request 的产生,却没有分析 $response 。 这节,我们就来研究下 Laravel 的 Response 对象: Illuminate\Http\Response

Illuminate\Http\Response 初识

不难看出,这句代码拿到的一定是个对象。为什么? 因为在下文,就调用了他的 ->send() 方法。 https://github.com/xiaohuilam/laravel/blob/7028b17ed8bf35ee2f1269c0f9c985b411cb4469/public/index.php#L58

那么 Illuminate\Http\Response 对象是从哪里来的呢?

在从 入口Kernel::handle()管道 的过程尾声,我们拿到了这句逻辑

https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Routing/Router.php#L674-L681https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Routing/Router.php#L678-L680

这个 prepareResponse 是干嘛的呢? https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Routing/Router.php#L717-L720

调用了 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Routing/Router.php#L729-L755

过程为:

于是乎,我们才可以在 controller 中大胆的直接返回字符串而转换成 Response 对象。因为在这里他帮我们转了。

经过上面这一波加工后,在 public/index.php 里面才敢执行 $response->send()

Illuminate\Http\Response::send() 的逻辑

https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/symfony/http-foundation/Response.php#L366-L378

调用的 sendContent 逻辑为 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/symfony/http-foundation/Response.php#L354-L359 其中的 echo 代码清晰可见,非常简单。

先于 sendContentsendHeaders 的代码要稍微复杂一点 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/symfony/http-foundation/Response.php#L324-L347

先是将 http 响应头依次输出,然后将 Cookie 处理后输出。

send 方法的后面,还有几句 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/symfony/http-foundation/Response.php#L371-L375 作用是判断是否是 fastcgi 或者 cli 模式调用,如果是就关闭连接、刷新缓冲区。

至此,send 逻辑就讲完了。

Kernel::terminate() 的逻辑

App\Http\Kernel 并没有 terminate 方法,一路穿透到了 Illuminate\Foundation\Http\Kernel 里面 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php#L187-L192

Illuminate\Foundation\Http\Kernel::terminateMiddleware() 逻辑 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php#L201-L221 将容器中 bind 过的中间件 terminate() 掉。

而这句就比较好理解了,直接调用了容器的 terminate() https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php#L191

容器的 terminate() 方法也比较简单,触发 $terminatingCallbacks 注册的回调。 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Foundation/Application.php#L962-L967

PHP的不常驻特性,一个请求完了就自动回收资源,那么除了需要触发 $terminatingCallbacks 回调外,为什么还要实现 terminate() 呢?

Laravel 为我们提供了一个 php artisan serve 的便捷服务器,启动后 Application 对象是常驻的。所以需要单独回收需要回收的资源。

php artisan serve 的本质也是 php -S,详细资料 PHP 的命令行模式>内置Web Server

扩展阅读:在 Laravel 5.3 以前,Kernel::terminate() 还调用了 fastcgi_finish_request()。后来移除了,主要原因是因为这个函数会阻断 http 响应的头和 body,造成部分特殊情况的 session 问题,不过也可能还有其他原因。