摘要:設(shè)置生成對象后就要執(zhí)行對象的方法了,該方法定義在類中,其主要目的是對進(jìn)行微調(diào)使其能夠遵從協(xié)議。最后會(huì)把完整的響應(yīng)發(fā)送給客戶端。本文已經(jīng)收錄在系列文章源碼學(xué)習(xí)里,歡迎訪問閱讀。
Response
前面兩節(jié)我們分別講了Laravel的控制器和Request對象,在講Request對象的那一節(jié)我們看了Request對象是如何被創(chuàng)建出來的以及它支持的方法都定義在哪里,講控制器時(shí)我們詳細(xì)地描述了如何找到Request對應(yīng)的控制器方法然后執(zhí)行處理程序的,本節(jié)我們就來說剩下的那一部分,控制器方法的執(zhí)行結(jié)果是如何被轉(zhuǎn)換成響應(yīng)對象Response然后返回給客戶端的。
創(chuàng)建Response讓我們回到Laravel執(zhí)行路由處理程序返回響應(yīng)的代碼塊:
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; //收集路由和控制器里應(yīng)用的中間件 $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() ); }); } }
在講控制器的那一節(jié)里我們已經(jīng)提到過runRouteWithinStack方法里是最終執(zhí)行路由處理程序(控制器方法或者閉包處理程序)的地方,通過上面的代碼我們也可以看到執(zhí)行的結(jié)果會(huì)傳遞給Router的prepareResponse方法,當(dāng)程序流返回到runRoute里后又執(zhí)行了一次prepareResponse方法得到了要返回給客戶端的Response對象, 下面我們就來詳細(xì)看一下prepareResponse方法。
class Router implements RegistrarContract, BindingRegistrar { /** * 通過給定值創(chuàng)建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規(guī)范中對服務(wù)端響應(yīng)的定義 |
IlluminateHttpJsonResponse (SymfonyComponentHttpFoundationResponse的子類) | Laravel中對服務(wù)端JSON響應(yīng)的定義 |
IlluminateHttpResponse (SymfonyComponentHttpFoundationResponse的子類) | Laravel中對普通的非JSON響應(yīng)的定義 |
通過prepareResponse中的邏輯可以看到,無論路由執(zhí)行結(jié)果返回的是什么值最終都會(huì)被Laravel轉(zhuǎn)換為成一個(gè)Response對象,而這些對象都是SymfonyComponentHttpFoundationResponse類或者其子類的對象。從這里也就能看出來跟Request一樣Laravel的Response也是依賴Symfony框架的HttpFoundation組件來實(shí)現(xiàn)的。
我們來看一下SymfonyComponentHttpFoundationResponse的構(gòu)造方法:
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"); } //設(shè)置響應(yīng)的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; } }
所以路由處理程序的返回值在創(chuàng)業(yè)Response對象時(shí)會(huì)設(shè)置到對象的content屬性里,該屬性的值就是返回給客戶端的響應(yīng)的響應(yīng)內(nèi)容。
設(shè)置Response headers生成Response對象后就要執(zhí)行對象的prepare方法了,該方法定義在SymfonyComponentHttpFoundationResposne類中,其主要目的是對Response進(jìn)行微調(diào)使其能夠遵從HTTP/1.1協(xié)議(RFC 2616)。
namespace SymfonyComponentHttpFoundation; class Response { //在響應(yīng)被發(fā)送給客戶端之前對其進(jìn)行修訂使其能遵從HTTP/1.1協(xié)議 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里針對各種情況設(shè)置了相應(yīng)的response header 比如Content-Type、Content-Length等等這些我們常見的首部字段。
發(fā)送Response創(chuàng)建并設(shè)置完Response后它會(huì)流經(jīng)路由和框架中間件的后置操作,在中間件的后置操作里一般都是對Response進(jìn)行進(jìn)一步加工,最后程序流回到Http Kernel那里, Http Kernel會(huì)把Response發(fā)送給客戶端,我們來看一下這部分的代碼。
//入口文件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; } //發(fā)送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; } //發(fā)送響應(yīng)內(nèi)容到客戶端 public function sendContent() { echo $this->content; return $this; } }
send的邏輯就非常好理解了,把之前設(shè)置好的那些headers設(shè)置到HTTP響應(yīng)的首部字段里,Content會(huì)echo后被設(shè)置到HTTP響應(yīng)的主體實(shí)體中。最后PHP會(huì)把完整的HTTP響應(yīng)發(fā)送給客戶端。
send響應(yīng)后Http Kernel會(huì)執(zhí)行terminate方法調(diào)用terminate中間件里的terminate方法,最后執(zhí)行應(yīng)用的termiate方法來結(jié)束整個(gè)應(yīng)用生命周期(從接收請求開始到返回響應(yīng)結(jié)束)。
本文已經(jīng)收錄在系列文章Laravel源碼學(xué)習(xí)里,歡迎訪問閱讀。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/28711.html
摘要:對于包含通配符的事件名,會(huì)被統(tǒng)一放入數(shù)組中,是用來創(chuàng)建事件對應(yīng)的的如果是監(jiān)聽器是類,去創(chuàng)建監(jiān)聽類創(chuàng)建的時(shí)候,會(huì)判斷監(jiān)聽對象是監(jiān)聽類還是閉包函數(shù)。對于閉包監(jiān)聽來說,會(huì)再包裝一層返回一個(gè)閉包函數(shù)作為事件的監(jiān)聽者。 事件系統(tǒng) Laravel 的事件提供了一個(gè)簡單的觀察者實(shí)現(xiàn),能夠訂閱和監(jiān)聽?wèi)?yīng)用中發(fā)生的各種事件。事件機(jī)制是一種很好的應(yīng)用解耦方式,因?yàn)橐粋€(gè)事件可以擁有多個(gè)互不依賴的監(jiān)聽器。lar...
摘要:擴(kuò)展用戶認(rèn)證系統(tǒng)上一節(jié)我們介紹了系統(tǒng)實(shí)現(xiàn)的一些細(xì)節(jié)知道了是如何應(yīng)用看守器和用戶提供器來進(jìn)行用戶認(rèn)證的,但是針對我們自己開發(fā)的項(xiàng)目或多或少地我們都會(huì)需要在自帶的看守器和用戶提供器基礎(chǔ)之上做一些定制化來適應(yīng)項(xiàng)目,本節(jié)我會(huì)列舉一個(gè)在做項(xiàng)目時(shí)遇到的 擴(kuò)展用戶認(rèn)證系統(tǒng) 上一節(jié)我們介紹了Laravel Auth系統(tǒng)實(shí)現(xiàn)的一些細(xì)節(jié)知道了Laravel是如何應(yīng)用看守器和用戶提供器來進(jìn)行用戶認(rèn)證的,但是...
摘要:解析出后將進(jìn)入應(yīng)用的請求對象傳遞給的方法,在方法負(fù)責(zé)處理流入應(yīng)用的請求對象并返回響應(yīng)對象。攜帶了本次迭代的值。通過這種方式讓請求對象依次流過了要通過的中間件,達(dá)到目的地的方法。 中間件(Middleware)在Laravel中起著過濾進(jìn)入應(yīng)用的HTTP請求對象(Request)和完善離開應(yīng)用的HTTP響應(yīng)對象(Reponse)的作用, 而且可以通過應(yīng)用多個(gè)中間件來層層過濾請求、逐步完善...
摘要:過去一年時(shí)間寫了多篇文章來探討了我認(rèn)為的框架最核心部分的設(shè)計(jì)思路代碼實(shí)現(xiàn)。為了大家閱讀方便,我把這些源碼學(xué)習(xí)的文章匯總到這里。數(shù)據(jù)庫算法和數(shù)據(jù)結(jié)構(gòu)這些都是編程的內(nèi)功,只有內(nèi)功深厚了才能解決遇到的復(fù)雜問題。 過去一年時(shí)間寫了20多篇文章來探討了我認(rèn)為的Larave框架最核心部分的設(shè)計(jì)思路、代碼實(shí)現(xiàn)。通過更新文章自己在軟件設(shè)計(jì)、文字表達(dá)方面都有所提高,在剛開始決定寫Laravel源碼分析地...
摘要:調(diào)用了的可以看出,所有服務(wù)提供器都在配置文件文件的數(shù)組中。啟動(dòng)的啟動(dòng)由類負(fù)責(zé)引導(dǎo)應(yīng)用的屬性中記錄的所有服務(wù)提供器,就是依次調(diào)用這些服務(wù)提供器的方法,引導(dǎo)完成后就代表應(yīng)用正式啟動(dòng)了,可以開始處理請求了。 服務(wù)提供器是所有 Laravel 應(yīng)用程序引導(dǎo)中心。你的應(yīng)用程序自定義的服務(wù)、第三方資源包提供的服務(wù)以及 Laravel 的所有核心服務(wù)都是通過服務(wù)提供器進(jìn)行注冊(register)和引...
閱讀 781·2021-11-09 09:47
閱讀 1568·2019-08-30 15:44
閱讀 1143·2019-08-26 13:46
閱讀 2107·2019-08-26 13:41
閱讀 1266·2019-08-26 13:32
閱讀 3772·2019-08-26 10:35
閱讀 3519·2019-08-23 17:16
閱讀 448·2019-08-23 17:07