前面兩節咱們分別講了Laravel的控制器和Request對象,在講Request對象的那一節咱們看了Request對象是如何被建立出來的以及它支持的方法都定義在哪裏,講控制器時咱們詳細地描述瞭如何找到Request對應的控制器方法而後執行處理程序的,本節咱們就來講剩下的那一部分,控制器方法的執行結果是如何被轉換成響應對象Response而後返回給客戶端的。php
讓咱們回到Laravel執行路由處理程序返回響應的代碼塊:html
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
方法裏是最終執行路由處理程序(控制器方法或者閉包處理程序)的地方,經過上面的代碼咱們也能夠看到執行的結果會傳遞給Router
的prepareResponse
方法,當程序流返回到runRoute
裏後又執行了一次prepareResponse
方法獲得了要返回給客戶端的Response對象, 下面咱們就來詳細看一下prepareResponse
方法。git
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:github
Class Name | Representation |
---|---|
PsrResponseInterface(PsrHttpMessageResponseInterface的別名) | Psr規範中對服務端響應的定義 |
IlluminateHttpJsonResponse (SymfonyComponentHttpFoundationResponse的子類) | Laravel中對服務端JSON響應的定義 |
IlluminateHttpResponse (SymfonyComponentHttpFoundationResponse的子類) | Laravel中對普通的非JSON響應的定義 |
經過prepareResponse
中的邏輯能夠看到,不管路由執行結果返回的是什麼值最終都會被Laravel轉換爲成一個Response對象,而這些對象都是SymfonyComponentHttpFoundationResponse類或者其子類的對象。從這裏也就能看出來跟Request同樣Laravel的Response也是依賴Symfony框架的HttpFoundation
組件來實現的。cookie
咱們來看一下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屬性裏,該屬性的值就是返回給客戶端的響應的響應內容。app
生成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-Type
、Content-Length
等等這些咱們常見的首部字段。學習
建立並設置完Response後它會流經路由和框架中間件的後置操做,在中間件的後置操做裏通常都是對Response進行進一步加工,最後程序流回到Http Kernel那裏, Http Kernel會把Response發送給客戶端,咱們來看一下這部分的代碼。this
//入口文件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
方法來結束整個應用生命週期(從接收請求開始到返回響應結束)。
本文已經收錄在系列文章Laravel源碼學習裏,歡迎訪問閱讀。