Laravel核心解讀Response

來源:互聯網
上載者:User
這篇文章主要介紹了關於Laravel核心解讀Response,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下

Response

前面兩節我們分別講了Laravel的控制器和Request對象,在講Request對象的那一節我們看了Request對象是如何被建立出來的以及它支援的方法都定義在哪裡,講控制器時我們詳細地描述了如何找到Request對應的控制器方法然後執行處理常式的,本節我們就來說剩下的那一部分,控制器方法的執行結果是如何被轉換成響應對象Response然後返回給用戶端的。

建立Response

讓我們回到Laravel執行路由處理常式返迴響應的代碼塊:

namespace Illuminate\Routing;class Router implements RegistrarContract, BindingRegistrar{         protected function runRoute(Request $request, Route $route)    {        $request->setRouteResolver(function () use ($route) {            return $route;        });        $this->events->dispatch(new Events\RouteMatched($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  \Symfony\Component\HttpFoundation\Request  $request     * @param  mixed  $response     * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse     */    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 Symfony\Component\HttpFoundation;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方法了,該方法定義在Symfony\Component\HttpFoundation\Resposne類中,其主要目的是對Response進行微調使其能夠遵從HTTP/1.1協議(RFC 2616)。

namespace Symfony\Component\HttpFoundation;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(Illuminate\Contracts\Http\Kernel::class);$response = $kernel->handle(    $request = Illuminate\Http\Request::capture());$response->send();$kernel->terminate($request, $response);
namespace Symfony\Component\HttpFoundation;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方法來結束整個應用生命週期(從接收請求開始到返迴響應結束)。

以上就是本文的全部內容,希望對大家的學習有所協助,更多相關內容請關注topic.alibabacloud.com!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.