资讯专栏INFORMATION COLUMN

Laravel核心解读 -- Response

TigerChain / 3284人阅读

摘要:设置生成对象后就要执行对象的方法了,该方法定义在类中,其主要目的是对进行微调使其能够遵从协议。最后会把完整的响应发送给客户端。本文已经收录在系列文章源码学习里,欢迎访问阅读。

Response

前面两节我们分别讲了Laravel的控制器和Request对象,在讲Request对象的那一节我们看了Request对象是如何被创建出来的以及它支持的方法都定义在哪里,讲控制器时我们详细地描述了如何找到Request对应的控制器方法然后执行处理程序的,本节我们就来说剩下的那一部分,控制器方法的执行结果是如何被转换成响应对象Response然后返回给客户端的。

创建Response

让我们回到Laravel执行路由处理程序返回响应的代码块:

namespace IlluminateRouting;
class Router implements RegistrarContract, BindingRegistrar
{     
    protected function runRoute(Request $request, Route $route)
    {
        $request->setRouteResolver(function () use ($route) {
            return $route;
        });

        $this->events->dispatch(new EventsRouteMatched($route, $request));

        return $this->prepareResponse($request,
            $this->runRouteWithinStack($route, $request)
        );
    }
    
    protected function runRouteWithinStack(Route $route, Request $request)
    {
        $shouldSkipMiddleware = $this->container->bound("middleware.disable") &&
                            $this->container->make("middleware.disable") === true;
        //收集路由和控制器里应用的中间件
        $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);

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

在讲控制器的那一节里我们已经提到过runRouteWithinStack方法里是最终执行路由处理程序(控制器方法或者闭包处理程序)的地方,通过上面的代码我们也可以看到执行的结果会传递给RouterprepareResponse方法,当程序流返回到runRoute里后又执行了一次prepareResponse方法得到了要返回给客户端的Response对象, 下面我们就来详细看一下prepareResponse方法。

class Router implements RegistrarContract, BindingRegistrar
{
    /**
     * 通过给定值创建Response对象
     *
     * @param  SymfonyComponentHttpFoundationRequest  $request
     * @param  mixed  $response
     * @return IlluminateHttpResponse|IlluminateHttpJsonResponse
     */
    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 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);
    }
}

在上面的代码中我们看到有三种Response:

Class Name Representation
PsrResponseInterface(PsrHttpMessageResponseInterface的别名) Psr规范中对服务端响应的定义
IlluminateHttpJsonResponse (SymfonyComponentHttpFoundationResponse的子类) Laravel中对服务端JSON响应的定义
IlluminateHttpResponse (SymfonyComponentHttpFoundationResponse的子类) Laravel中对普通的非JSON响应的定义

通过prepareResponse中的逻辑可以看到,无论路由执行结果返回的是什么值最终都会被Laravel转换为成一个Response对象,而这些对象都是SymfonyComponentHttpFoundationResponse类或者其子类的对象。从这里也就能看出来跟Request一样Laravel的Response也是依赖Symfony框架的HttpFoundation组件来实现的。

我们来看一下SymfonyComponentHttpFoundationResponse的构造方法:

namespace SymfonyComponentHttpFoundation;
class Response
{
    public function __construct($content = "", $status = 200, $headers = array())
    {
        $this->headers = new ResponseHeaderBag($headers);
        $this->setContent($content);
        $this->setStatusCode($status);
        $this->setProtocolVersion("1.0");
    }
    //设置响应的Content
    public function setContent($content)
    {
        if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable(array($content, "__toString"))) {
            throw new UnexpectedValueException(sprintf("The Response content must be a string or object implementing __toString(), "%s" given.", gettype($content)));
        }

        $this->content = (string) $content;

        return $this;
    }
}

所以路由处理程序的返回值在创业Response对象时会设置到对象的content属性里,该属性的值就是返回给客户端的响应的响应内容。

设置Response headers

生成Response对象后就要执行对象的prepare方法了,该方法定义在SymfonyComponentHttpFoundationResposne类中,其主要目的是对Response进行微调使其能够遵从HTTP/1.1协议(RFC 2616)。

namespace SymfonyComponentHttpFoundation;
class Response
{
    //在响应被发送给客户端之前对其进行修订使其能遵从HTTP/1.1协议
    public function prepare(Request $request)
    {
        $headers = $this->headers;

        if ($this->isInformational() || $this->isEmpty()) {
            $this->setContent(null);
            $headers->remove("Content-Type");
            $headers->remove("Content-Length");
        } else {
            // Content-type based on the Request
            if (!$headers->has("Content-Type")) {
                $format = $request->getRequestFormat();
                if (null !== $format && $mimeType = $request->getMimeType($format)) {
                    $headers->set("Content-Type", $mimeType);
                }
            }

            // Fix Content-Type
            $charset = $this->charset ?: "UTF-8";
            if (!$headers->has("Content-Type")) {
                $headers->set("Content-Type", "text/html; charset=".$charset);
            } elseif (0 === stripos($headers->get("Content-Type"), "text/") && false === stripos($headers->get("Content-Type"), "charset")) {
                // add the charset
                $headers->set("Content-Type", $headers->get("Content-Type")."; charset=".$charset);
            }

            // Fix Content-Length
            if ($headers->has("Transfer-Encoding")) {
                $headers->remove("Content-Length");
            }

            if ($request->isMethod("HEAD")) {
                // cf. RFC2616 14.13
                $length = $headers->get("Content-Length");
                $this->setContent(null);
                if ($length) {
                    $headers->set("Content-Length", $length);
                }
            }
        }

        // Fix protocol
        if ("HTTP/1.0" != $request->server->get("SERVER_PROTOCOL")) {
            $this->setProtocolVersion("1.1");
        }

        // Check if we need to send extra expire info headers
        if ("1.0" == $this->getProtocolVersion() && false !== strpos($this->headers->get("Cache-Control"), "no-cache")) {
            $this->headers->set("pragma", "no-cache");
            $this->headers->set("expires", -1);
        }

        $this->ensureIEOverSSLCompatibility($request);

        return $this;
    }
}

prepare里针对各种情况设置了相应的response header 比如Content-TypeContent-Length等等这些我们常见的首部字段。

发送Response

创建并设置完Response后它会流经路由和框架中间件的后置操作,在中间件的后置操作里一般都是对Response进行进一步加工,最后程序流回到Http Kernel那里, Http Kernel会把Response发送给客户端,我们来看一下这部分的代码。

//入口文件public/index.php
$kernel = $app->make(IlluminateContractsHttpKernel::class);

$response = $kernel->handle(
    $request = IlluminateHttpRequest::capture()
);

$response->send();

$kernel->terminate($request, $response);
namespace SymfonyComponentHttpFoundation;
class Response
{
    public function send()
    {
        $this->sendHeaders();
        $this->sendContent();

        if (function_exists("fastcgi_finish_request")) {
            fastcgi_finish_request();
        } elseif ("cli" !== PHP_SAPI) {
            static::closeOutputBuffers(0, true);
        }

        return $this;
    }
    
    //发送headers到客户端
    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);
            }
        }

        // status
        header(sprintf("HTTP/%s %s %s", $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);

        // cookies
        foreach ($this->headers->getCookies() as $cookie) {
            if ($cookie->isRaw()) {
                setrawcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
            } else {
                setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
            }
        }

        return $this;
    }
    
    //发送响应内容到客户端
    public function sendContent()
    {
        echo $this->content;

        return $this;
    }
}

send的逻辑就非常好理解了,把之前设置好的那些headers设置到HTTP响应的首部字段里,Content会echo后被设置到HTTP响应的主体实体中。最后PHP会把完整的HTTP响应发送给客户端。

send响应后Http Kernel会执行terminate方法调用terminate中间件里的terminate方法,最后执行应用的termiate方法来结束整个应用生命周期(从接收请求开始到返回响应结束)。

本文已经收录在系列文章Laravel源码学习里,欢迎访问阅读。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/28711.html

相关文章

  • Laravel核心解读 -- 事件系统

    摘要:对于包含通配符的事件名,会被统一放入数组中,是用来创建事件对应的的如果是监听器是类,去创建监听类创建的时候,会判断监听对象是监听类还是闭包函数。对于闭包监听来说,会再包装一层返回一个闭包函数作为事件的监听者。 事件系统 Laravel 的事件提供了一个简单的观察者实现,能够订阅和监听应用中发生的各种事件。事件机制是一种很好的应用解耦方式,因为一个事件可以拥有多个互不依赖的监听器。lar...

    chengjianhua 评论0 收藏0
  • Laravel核心解读 -- 扩展用户认证系统

    摘要:扩展用户认证系统上一节我们介绍了系统实现的一些细节知道了是如何应用看守器和用户提供器来进行用户认证的,但是针对我们自己开发的项目或多或少地我们都会需要在自带的看守器和用户提供器基础之上做一些定制化来适应项目,本节我会列举一个在做项目时遇到的 扩展用户认证系统 上一节我们介绍了Laravel Auth系统实现的一些细节知道了Laravel是如何应用看守器和用户提供器来进行用户认证的,但是...

    王伟廷 评论0 收藏0
  • Laravel核心解读--中间件(Middleware)

    摘要:解析出后将进入应用的请求对象传递给的方法,在方法负责处理流入应用的请求对象并返回响应对象。携带了本次迭代的值。通过这种方式让请求对象依次流过了要通过的中间件,达到目的地的方法。 中间件(Middleware)在Laravel中起着过滤进入应用的HTTP请求对象(Request)和完善离开应用的HTTP响应对象(Reponse)的作用, 而且可以通过应用多个中间件来层层过滤请求、逐步完善...

    enda 评论0 收藏0
  • Laravel核心解读--完结篇

    摘要:过去一年时间写了多篇文章来探讨了我认为的框架最核心部分的设计思路代码实现。为了大家阅读方便,我把这些源码学习的文章汇总到这里。数据库算法和数据结构这些都是编程的内功,只有内功深厚了才能解决遇到的复杂问题。 过去一年时间写了20多篇文章来探讨了我认为的Larave框架最核心部分的设计思路、代码实现。通过更新文章自己在软件设计、文字表达方面都有所提高,在刚开始决定写Laravel源码分析地...

    laoLiueizo 评论0 收藏0
  • Laravel核心解读--服务提供器(ServiceProvider)

    摘要:调用了的可以看出,所有服务提供器都在配置文件文件的数组中。启动的启动由类负责引导应用的属性中记录的所有服务提供器,就是依次调用这些服务提供器的方法,引导完成后就代表应用正式启动了,可以开始处理请求了。 服务提供器是所有 Laravel 应用程序引导中心。你的应用程序自定义的服务、第三方资源包提供的服务以及 Laravel 的所有核心服务都是通过服务提供器进行注册(register)和引...

    Richard_Gao 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<