Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
xiaohuilam opened this issue Sep 28, 2018 · 0 comments
Open

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

xiaohuilam opened this issue Sep 28, 2018 · 0 comments
Labels
book The digital book for laravel learning

Comments

@xiaohuilam
Copy link
Owner

xiaohuilam commented Sep 28, 2018

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

@xiaohuilam xiaohuilam changed the title 14. Response 对象详解[0%] 14. Response 对象和 Kernel::terminate 详解[0%] Sep 28, 2018
@xiaohuilam xiaohuilam changed the title 14. Response 对象和 Kernel::terminate 详解[0%] 14. Response 对象和 Kernel::terminate() 详解[0%] Sep 28, 2018
@xiaohuilam xiaohuilam changed the title 14. Response 对象和 Kernel::terminate() 详解[0%] 14. Response 对象和 Kernel::terminate() Sep 28, 2018
@xiaohuilam xiaohuilam added the book The digital book for laravel learning label Sep 28, 2018
@xiaohuilam xiaohuilam changed the title 14. Response 对象和 Kernel::terminate() 14. Response 对象和 Kernel::terminate Sep 28, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
book The digital book for laravel learning
Projects
None yet
Development

No branches or pull requests

1 participant