xiaohuilam / laravel

Laravel 深入详解 —— 源代码解析,新手进阶指南

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

14. Response 对象和 Kernel::terminate

xiaohuilam opened this issue · comments

在前面 01. HTTP 入口解析 的第 54-56 行代码分析时

laravel/public/index.php

Lines 54 to 56 in 7028b17

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

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

Illuminate\Http\Response 初识

不难看出,这句代码拿到的一定是个对象。为什么?
因为在下文,就调用了他的 ->send() 方法。

$response->send();

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

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

return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});

return $this->prepareResponse(
$request, $route->run()
);

这个 prepareResponse 是干嘛的呢?

public function prepareResponse($request, $response)
{
return static::toResponse($request, $response);
}

调用了

public static function toResponse($request, $response)
{
if ($response instanceof Responsable) {
$response = $response->toResponse($request);
}
if ($response instanceof PsrResponseInterface) {
$response = (new HttpFoundationFactory)->createResponse($response);
} elseif ($response instanceof Model && $response->wasRecentlyCreated) {
$response = new JsonResponse($response, 201);
} elseif (! $response instanceof SymfonyResponse &&
($response instanceof Arrayable ||
$response instanceof Jsonable ||
$response instanceof ArrayObject ||
$response instanceof JsonSerializable ||
is_array($response))) {
$response = new JsonResponse($response);
} elseif (! $response instanceof SymfonyResponse) {
$response = new Response($response);
}
if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
$response->setNotModified();
}
return $response->prepare($request);
}

过程为:

  • 判断是否为 PsrResponseInterface 对象;
  • 则判断为模型,且刚创建,将响应头 statusCoe 强制覆盖成 HTTP 201 created
  • 否则判断返回的对象是否具有序列号特征,如果是就重置成 JsonResponse
  • 否则的话覆盖成 Response

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

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

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

public function send()
{
$this->sendHeaders();
$this->sendContent();
if (\function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif (!\in_array(\PHP_SAPI, array('cli', 'phpdbg'), true)) {
static::closeOutputBuffers(0, true);
}
return $this;
}

调用的 sendContent 逻辑为

public function sendContent()
{
echo $this->content;
return $this;
}
其中的 echo 代码清晰可见,非常简单。

先于 sendContentsendHeaders 的代码要稍微复杂一点

public function sendHeaders()
{
// headers have already been sent by the developer
if (headers_sent()) {
return $this;
}
// headers
foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
foreach ($values as $value) {
header($name.': '.$value, false, $this->statusCode);
}
}
// cookies
foreach ($this->headers->getCookies() as $cookie) {
header('Set-Cookie: '.$cookie->getName().strstr($cookie, '='), false, $this->statusCode);
}
// status
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
return $this;
}

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

send 方法的后面,还有几句

if (\function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif (!\in_array(\PHP_SAPI, array('cli', 'phpdbg'), true)) {
static::closeOutputBuffers(0, true);
}
作用是判断是否是 fastcgi 或者 cli 模式调用,如果是就关闭连接、刷新缓冲区。

至此,send 逻辑就讲完了。

Kernel::terminate() 的逻辑

App\Http\Kernel 并没有 terminate 方法,一路穿透到了 Illuminate\Foundation\Http\Kernel 里面

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

Illuminate\Foundation\Http\Kernel::terminateMiddleware() 逻辑

protected function terminateMiddleware($request, $response)
{
$middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
$this->gatherRouteMiddleware($request),
$this->middleware
);
foreach ($middlewares as $middleware) {
if (! is_string($middleware)) {
continue;
}
list($name) = $this->parseMiddleware($middleware);
$instance = $this->app->make($name);
if (method_exists($instance, 'terminate')) {
$instance->terminate($request, $response);
}
}
}

将容器中 bind 过的中间件 terminate() 掉。

而这句就比较好理解了,直接调用了容器的 terminate()

容器的 terminate() 方法也比较简单,触发 $terminatingCallbacks 注册的回调。

public function terminate()
{
foreach ($this->terminatingCallbacks as $terminating) {
$this->call($terminating);
}
}

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 问题,不过也可能还有其他原因。