Laravel Response 响应客户端

本篇文章逻辑较长,只说明和响应生命周期相关的必要代码。

本文主要内容顺序为:

1、执行上文管道中的then方法指定的闭包,路由的分发

2、在路由器中(Router类)找到请求($request 也就是经过全局中间件处理的请求)匹配的路由规则

3、说明路由规则的加载(会跳转到框架的boot过程),注意这部分是在处理请求之前完成的,因为一旦当我们开始处理请求,就意味着所有的路由都应该已经加载好了,供我们的请求进行匹配

4、执行请求匹配到的路由逻辑

5、生成响应,并发送给客户端

6、最后生命周期的结束

7、基本响应类的使用

前文说道,如果一个请求顺利通过了全局中间件那么就会调用管道then方法中传入的闭包

  1. protected function sendRequestThroughRouter($request)
  2. {
  3. $this->app->instance('request', $request);
  4. Facade::clearResolvedInstance('request');
  5. $this->bootstrap();
  6. // 代码如下
  7. return (new Pipeline($this->app))
  8. ->send($request)
  9. ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
  10. // 此方法将当前请求挂载到容器,然后执行路由器的分发
  11. ->then($this->dispatchToRouter());
  12. }
  13. protected function dispatchToRouter()
  14. {
  15. return function ($request) {
  16. $this->app->instance('request', $request);
  17. return $this->router->dispatch($request);
  18. };
  19. }

查看Illuminate\Routing\Router::dispatch方法

  1. public function dispatch(Request $request)
  2. {
  3. $this->currentRequest = $request;
  4. // 将请求分发到路由
  5. // 跳转到dispatchToRoute方法
  6. return $this->dispatchToRoute($request);
  7. }
  8. public function dispatchToRoute(Request $request)
  9. {
  10. // 先跳转到findRoute方法
  11. return $this->runRoute($request, $this->findRoute($request));
  12. }
  13. // 见名之意 通过给定的$request 找到匹配的路由
  14. protected function findRoute($request)
  15. {
  16. // 跳转到Illuminate\Routing\RouteCollection::match方法
  17. $this->current = $route = $this->routes->match($request);
  18. $this->container->instance(Route::class, $route);
  19. return $route;
  20. }

查看Illuminate\Routing\RouteCollection::match方法

  1. /**
  2. * Find the first route matching a given request.
  3. *
  4. * @param \Illuminate\Http\Request $request
  5. * @return \Illuminate\Routing\Route
  6. *
  7. * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
  8. */
  9. public function match(Request $request)
  10. {
  11. // 根据请求动作找到全局匹配的路由
  12. // 可以自行打印下$routes
  13. $routes = $this->get($request->getMethod());
  14. // 匹配路由 下面查看框架如何生成的路由规则!!!
  15. $route = $this->matchAgainstRoutes($routes, $request);
  16. if (! is_null($route)) {
  17. return $route->bind($request);
  18. }
  19. $others = $this->checkForAlternateVerbs($request);
  20. if (count($others) > 0) {
  21. return $this->getRouteForMethods($request, $others);
  22. }
  23. throw new NotFoundHttpException;
  24. }

下面说明框架如何加载的路由规则

Application::boot方法

  1. // 主要逻辑是调用服务提供者的boot方法
  2. array_walk($this->serviceProviders, function ($p) {
  3. $this->bootProvider($p);
  4. });

App\Providers\RouteServiceProvider::boot方法

  1. public function boot()
  2. {
  3. // 调用父类Illuminate\Foundation\Support\Providers\RouteServiceProvider的boot方法
  4. parent::boot();
  5. }

Illuminate\Foundation\Support\Providers\RouteServiceProvider::boot方法

  1. public function boot()
  2. {
  3. $this->setRootControllerNamespace();
  4. if ($this->routesAreCached()) {
  5. $this->loadCachedRoutes();
  6. } else {
  7. // 就看这个loadRoutes方法
  8. $this->loadRoutes();
  9. $this->app->booted(function () {
  10. // dd(get_class($this->app['router']));
  11. $this->app['router']->getRoutes()->refreshNameLookups();
  12. $this->app['router']->getRoutes()->refreshActionLookups();
  13. });
  14. }
  15. }
  16. /**
  17. * Load the application routes.
  18. * 看注释就知道我们来对了地方
  19. * @return void
  20. */
  21. protected function loadRoutes()
  22. {
  23. // 调用App\Providers\RouteServiceProvider的map方法
  24. if (method_exists($this, 'map')) {
  25. $this->app->call([$this, 'map']);
  26. }
  27. }

App\Providers\RouteServiceProvider::map方法

  1. public function map()
  2. {
  3. // 为了调试方便我注释掉了api路由
  4. // $this->mapApiRoutes();
  5. // 这两个都是加载路由文件 这里查看web.php
  6. $this->mapWebRoutes();
  7. }
  8. protected function mapWebRoutes()
  9. {
  10. // 调用Router的__call方法 返回的是RouteRegistrar实例
  11. Route::middleware('web')
  12. ->namespace($this->namespace)
  13. // 调用RouteRegistrar的namespace方法 触发__call魔术方法
  14. // 依然是挂载属性 可自行打印
  15. // Illuminate\Routing\RouteRegistrar {#239 ▼
  16. // #router: Illuminate\Routing\Router {#34 }
  17. // #attributes: array:2 [▼
  18. // "middleware" => array:1 [▼
  19. // 0 => "web"
  20. // ]
  21. // "namespace" => "App\Http\Controllers"
  22. // ]
  23. // #passthru: array:7 []
  24. // #allowedAttributes: array:7 []
  25. // #aliases: array:1 []
  26. // }
  27. // 调用RouteRegistrar的group方法
  28. ->group(base_path('routes/web.php'));
  29. }

Router::__call方法

  1. public function __call($method, $parameters)
  2. {
  3. if (static::hasMacro($method)) {
  4. return $this->macroCall($method, $parameters);
  5. }
  6. if ($method === 'middleware') {
  7. // 调用了RouteRegistrar的attribute方法 只是挂载路由属性
  8. return (new RouteRegistrar($this))->attribute($method, is_array($parameters[0]) ? $parameters[0] : $parameters);
  9. }
  10. return (new RouteRegistrar($this))->attribute($method, $parameters[0]);
  11. }

Illuminate\Routing\RouteRegistrar::__call方法

  1. public function __call($method, $parameters)
  2. {
  3. if (in_array($method, $this->passthru)) {
  4. // 当使用get post等方法的时候
  5. return $this->registerRoute($method, ...$parameters);
  6. }
  7. if (in_array($method, $this->allowedAttributes)) {
  8. if ($method === 'middleware') {
  9. return $this->attribute($method, is_array($parameters[0]) ? $parameters[0] : $parameters);
  10. }
  11. // dd($method); // namespace
  12. return $this->attribute($method, $parameters[0]);
  13. }
  14. throw new BadMethodCallException(sprintf(
  15. 'Method %s::%s does not exist.', static::class, $method
  16. ));
  17. }

Illuminate\Routing\RouteRegistrar::group方法

  1. public function group($callback)
  2. {
  3. // dd($this->attributes, $callback);
  4. // array:2 [▼
  5. // "middleware" => array:1 [▼
  6. // 0 => "web"
  7. // ]
  8. // "namespace" => "App\Http\Controllers"
  9. // ]
  10. // "/home/vagrant/code/test1/routes/web.php"
  11. // 查看Router的group方法
  12. $this->router->group($this->attributes, $callback);
  13. }

Router::group方法

  1. public function group(array $attributes, $routes)
  2. {
  3. $this->updateGroupStack($attributes);
  4. // 查看loadRoutes方法 /home/vagrant/code/test1/routes/web.php
  5. $this->loadRoutes($routes);
  6. array_pop($this->groupStack);
  7. }
  8. protected function loadRoutes($routes)
  9. {
  10. if ($routes instanceof Closure) {
  11. // 用于闭包嵌套 laravel的路由是可以随意潜逃组合的
  12. $routes($this);
  13. } else {
  14. // 加载路由文件 /home/vagrant/code/test1/routes/web.php
  15. (new RouteFileRegistrar($this))->register($routes);
  16. }
  17. }

Illuminate\Routing\RouteFileRegistrar 文件

  1. protected $router;
  2. public function __construct(Router $router)
  3. {
  4. $this->router = $router;
  5. }
  6. public function register($routes)
  7. {
  8. $router = $this->router;
  9. // 终于加载到了路由文件
  10. // require("/home/vagrant/code/test1/routes/web.php");
  11. // 看到这里就到了大家熟悉的Route::get()等方法了
  12. // 道友们可能已经有了有趣的想法: 可以在web.php等路由文件中继续require其他文件
  13. // 便可实现不同功能模块的路由管理
  14. require $routes;
  15. }

了解了理由加载流程,下面举个简单例子,laravel如何注册一个路由

  1. // web.php中
  2. Route::get('routecontroller', "\App\Http\Controllers\Debug\TestController@index");
  3. // 跳转到Router的get方法
  4. /**
  5. * Register a new GET route with the router.
  6. *
  7. * @param string $uri
  8. * @param \Closure|array|string|callable|null $action
  9. * @return \Illuminate\Routing\Route
  10. */
  11. public function get($uri, $action = null)
  12. {
  13. // dump($uri, $action);
  14. // $uri = routecontroller
  15. // $action = \App\Http\Controllers\Debug\TestController@index
  16. // 跳转到addRoute方法
  17. return $this->addRoute(['GET', 'HEAD'], $uri, $action);
  18. }
  19. /**
  20. * Add a route to the underlying route collection.
  21. *
  22. * @param array|string $methods
  23. * @param string $uri
  24. * @param \Closure|array|string|callable|null $action
  25. * @return \Illuminate\Routing\Route
  26. */
  27. // ['GET', 'HEAD'], $uri, $action
  28. public function addRoute($methods, $uri, $action)
  29. {
  30. // routes是routecollection实例
  31. // 跳转到createRoute方法
  32. // 跳转到RouteCollection的add方法
  33. return $this->routes->add($this->createRoute($methods, $uri, $action));
  34. }
  35. /**
  36. * Create a new route instance.
  37. *
  38. * @param array|string $methods
  39. * @param string $uri
  40. * @param mixed $action
  41. * @return \Illuminate\Routing\Route
  42. */
  43. // ['GET', 'HEAD'], $uri, $action
  44. protected function createRoute($methods, $uri, $action)
  45. {
  46. // 跳转到actionReferencesController方法
  47. if ($this->actionReferencesController($action)) {
  48. $action = $this->convertToControllerAction($action);
  49. // dump($action);
  50. // array:2 [▼
  51. // "uses" => "\App\Http\Controllers\Debug\TestController@index"
  52. // "controller" => "\App\Http\Controllers\Debug\TestController@index"
  53. // ]
  54. }
  55. // 创建一个对应路由规则的Route实例 并且添加到routes(collection)中
  56. // 返回到上面的addRoute方法
  57. // 请自行查看Route的构造方法
  58. $route = $this->newRoute(
  59. // dump($this->prefix);
  60. // routecontroller
  61. $methods, $this->prefix($uri), $action
  62. );
  63. if ($this->hasGroupStack()) {
  64. $this->mergeGroupAttributesIntoRoute($route);
  65. }
  66. $this->addWhereClausesToRoute($route);
  67. return $route;
  68. }
  69. /**
  70. * Determine if the action is routing to a controller.
  71. *
  72. * @param array $action
  73. * @return bool
  74. */
  75. // 判断是否路由到一个控制器
  76. protected function actionReferencesController($action)
  77. {
  78. // 在此例子中Route::get方法传递的是一个字符串
  79. if (! $action instanceof Closure) {
  80. // 返回true
  81. return is_string($action) || (isset($action['uses']) && is_string($action['uses']));
  82. }
  83. return false;
  84. }

RouteCollection的add方法

  1. /**
  2. * Add a Route instance to the collection.
  3. *
  4. * @param \Illuminate\Routing\Route $route
  5. * @return \Illuminate\Routing\Route
  6. */
  7. public function add(Route $route)
  8. {
  9. // 跳转吧
  10. $this->addToCollections($route);
  11. $this->addLookups($route);
  12. // 最终一路返回到Router的get方法 所以我们可以直接打印web.php定义的路由规则
  13. return $route;
  14. }
  15. /**
  16. * Add the given route to the arrays of routes.
  17. *
  18. * @param \Illuminate\Routing\Route $route
  19. * @return void
  20. */
  21. protected function addToCollections($route)
  22. {
  23. $domainAndUri = $route->getDomain().$route->uri();
  24. // dump($route->getDomain(), $route->uri()); null routecontroller
  25. foreach ($route->methods() as $method) {
  26. // 将路由规则挂载到数组 方便匹配
  27. $this->routes[$method][$domainAndUri] = $route;
  28. }
  29. // 将路由规则挂载的数组 方便匹配
  30. $this->allRoutes[$method.$domainAndUri] = $route;
  31. }

至此就生成了一条路由 注意我这里将注册api路由进行了注释,并且保证web.php中只有一条路由规则

以上是路由的加载 这部分是在$this->bootstrap()方法中完成的,还远没有到达路由分发和匹配的阶段,希望大家能够理解,至此路由规则生成完毕 保存到了RouteCollection实例中,每个路由规则都是一个Route对象,供请求进行匹配

下面根据此条路由进行匹配,并执行返回结果

我们回到Illuminate\Routing\RouteCollection::match方法

  1. public function match(Request $request)
  2. {
  3. // 获取符合当前请求动作的所有路由
  4. // 是一个Route对象数组 每一个对象对应一个route规则
  5. $routes = $this->get($request->getMethod());
  6. // 匹配到当前请求路由
  7. $route = $this->matchAgainstRoutes($routes, $request);
  8. if (! is_null($route)) {
  9. // 将绑定了请求的Route实例返回
  10. return $route->bind($request);
  11. }
  12. $others = $this->checkForAlternateVerbs($request);
  13. if (count($others) > 0) {
  14. return $this->getRouteForMethods($request, $others);
  15. }
  16. throw new NotFoundHttpException;
  17. }
  18. // 该方法中大量使用了collect方法 请查看laravel手册
  19. protected function matchAgainstRoutes(array $routes, $request, $includingMethod = true)
  20. {
  21. // dump(get_class_methods(get_class(collect($routes))));
  22. // dump(collect($routes)->all()); // items数组 protected属性
  23. // dump(collect($routes)->items); // items属性是一个数组
  24. // 当注册一个兜底路由的时候 (通过Route::fallback方法)对应$route的isFallback会被设为true
  25. // partition方法根据传入的闭包将集合分成两部分
  26. // 具体实现可以查看手册 集合部分
  27. [$fallbacks, $routes] = collect($routes)->partition(function ($route) {
  28. return $route->isFallback;
  29. });
  30. // 将兜底路由放到集合后面 并且通过first方法找到第一个匹配的路由
  31. return $routes->merge($fallbacks)->first(function ($value) use ($request, $includingMethod) {
  32. return $value->matches($request, $includingMethod);
  33. });
  34. }

Router文件

  1. protected function findRoute($request)
  2. {
  3. // 可以打印$route 你会发现和你在web.php中打印的是同一个Route对象
  4. $this->current = $route = $this->routes->match($request);
  5. // 将匹配到的路由实例挂载到容器
  6. $this->container->instance(Route::class, $route);
  7. return $route;
  8. }
  9. public function dispatchToRoute(Request $request)
  10. {
  11. // 跳转到runRoute方法
  12. return $this->runRoute($request, $this->findRoute($request));
  13. }
  14. protected function runRoute(Request $request, Route $route)
  15. {
  16. // 给request帮顶当前的route 可以使用$request->route()方法 获取route实例
  17. // 你也可以随时在你的业务代码中通过容器获得当前Route实例
  18. // app(Illuminate\Routing\Route::class)
  19. $request->setRouteResolver(function () use ($route) {
  20. return $route;
  21. });
  22. $this->events->dispatch(new RouteMatched($route, $request));
  23. // 开始准备响应了
  24. return $this->prepareResponse($request,
  25. // 跳转到runRouteWithinStack方法
  26. $this->runRouteWithinStack($route, $request)
  27. );
  28. }
  29. protected function runRouteWithinStack(Route $route, Request $request)
  30. {
  31. $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
  32. $this->container->make('middleware.disable') === true;
  33. $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
  34. // 依旧是一个pipeline 我们跳转到$route->run方法
  35. return (new Pipeline($this->container))
  36. ->send($request)
  37. ->through($middleware)
  38. ->then(function ($request) use ($route) {
  39. return $this->prepareResponse(
  40. $request, $route->run()
  41. );
  42. });
  43. }

Route::run方法 注意此方法的返回值是直接从匹配的控制器或者闭包中返回的

  1. public function run()
  2. {
  3. $this->container = $this->container ?: new Container;
  4. try {
  5. // 如果是一个控制器路由规则
  6. // 显然我们的此条路由是一个控制器路由
  7. if ($this->isControllerAction()) {
  8. // 将执行的结果返回给$route->run()
  9. // 跳回到上面的prepareResponse方法
  10. return $this->runController();
  11. }
  12. // 如果是一个闭包路由规则ControllerDispatcher
  13. return $this->runCallable();
  14. } catch (HttpResponseException $e) {
  15. return $e->getResponse();
  16. }
  17. }
  18. /**
  19. * Run the route action and return the response.
  20. *
  21. * @return mixed
  22. *
  23. * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
  24. */
  25. protected function runController()
  26. {
  27. //
  28. return $this->controllerDispatcher()->dispatch(
  29. $this,
  30. // 通过容器解析当前路由控制器实例
  31. $this->getController(),
  32. // 获取当前路由控制器方法
  33. $this->getControllerMethod()
  34. );
  35. }

Illuminate\Routing\ControllerDispatcher::dispatch方法

  1. /**
  2. * Dispatch a request to a given controller and method.
  3. *
  4. * @param \Illuminate\Routing\Route $route
  5. * @param mixed $controller
  6. * @param string $method
  7. * @return mixed
  8. */
  9. public function dispatch(Route $route, $controller, $method)
  10. {
  11. $parameters = $this->resolveClassMethodDependencies(
  12. $route->parametersWithoutNulls(), $controller, $method
  13. );
  14. if (method_exists($controller, 'callAction')) {
  15. // 执行基类控制器中的callAction方法并返回执行结果
  16. return $controller->callAction($method, $parameters);
  17. }
  18. return $controller->{$method}(...array_values($parameters));
  19. }

控制器方法返回的结果到Router::runRouteWithinStack方法

  1. protected function runRouteWithinStack(Route $route, Request $request)
  2. {
  3. $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
  4. $this->container->make('middleware.disable') === true;
  5. $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
  6. return (new Pipeline($this->container))
  7. ->send($request)
  8. ->through($middleware)
  9. ->then(function ($request) use ($route) {
  10. return $this->prepareResponse(
  11. // 返回到这里 然后执行prepareResponse方法
  12. $request, $route->run()
  13. );
  14. });
  15. }
  16. // 实际调用的是toResponse方法
  17. // 注意这里的$response是直接从控制器中返回的任何东西
  18. public static function toResponse($request, $response)
  19. {
  20. if ($response instanceof Responsable) {
  21. // 我们当然可以直接从控制器中返回一个实现了Responsable接口的实例
  22. $response = $response->toResponse($request);
  23. }
  24. if ($response instanceof PsrResponseInterface) {
  25. // 什么??? laravel还支持psr7?? 当然了 后面会附上使用文档
  26. $response = (new HttpFoundationFactory)->createResponse($response);
  27. } elseif ($response instanceof Model && $response->wasRecentlyCreated) {
  28. // 知道为什么laravel允许直接返回一个模型了吗
  29. $response = new JsonResponse($response, 201);
  30. } elseif (! $response instanceof SymfonyResponse &&
  31. // 知道laravel为什么允许你直接返回数组了吗
  32. ($response instanceof Arrayable ||
  33. $response instanceof Jsonable ||
  34. $response instanceof ArrayObject ||
  35. $response instanceof JsonSerializable ||
  36. is_array($response))) {
  37. $response = new JsonResponse($response);
  38. } elseif (! $response instanceof SymfonyResponse) {
  39. // 如果没匹配到 比如response是一个字符串,null等 直接生成响应类
  40. // 我们从laravel的Response构造方法开始梳理
  41. $response = new Response($response);
  42. }
  43. if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
  44. $response->setNotModified();
  45. }
  46. return $response->prepare($request);
  47. }

首先我们来看直接生成laravel响应 Illuminate\Http\Response

继承了Symfony\Component\HttpFoundation\Response

  1. // Symfony\Component\HttpFoundation\Response
  2. public function __construct($content = '', int $status = 200, array $headers = [])
  3. {
  4. // 可以看到基本什么都没做
  5. $this->headers = new ResponseHeaderBag($headers);
  6. // 调用Illuminate\Http\Response的setContent方法 设置响应内容呗
  7. $this->setContent($content);
  8. $this->setStatusCode($status);
  9. $this->setProtocolVersion('1.0');
  10. }
  11. // Illuminate\Http\Response::setContent
  12. public function setContent($content)
  13. {
  14. $this->original = $content;
  15. // shouldBeJson方法将实现了特定接口的response或者是一个array的response转换为
  16. // 并设置响应头
  17. if ($this->shouldBeJson($content)) {
  18. $this->header('Content-Type', 'application/json');
  19. // morphToJson方法保证最终给此响应设置的响应内容为json串
  20. $content = $this->morphToJson($content);
  21. }
  22. elseif ($content instanceof Renderable) {
  23. $content = $content->render();
  24. }
  25. // Symfony\Component\HttpFoundation\Response 如果最终设置的响应内容不是null或者字符串或者实现了__toString方法的类 那么跑出异常, 否则设置响应内容
  26. parent::setContent($content);
  27. return $this;
  28. }
  29. // Symfony\Component\HttpFoundation\Response::setContent方法
  30. public function setContent($content)
  31. {
  32. if (null !== $content && !\is_string($content) && !is_numeric($content) && !\is_callable([$content, '__toString'])) {
  33. // php官方建议不要使用gettype方法获取变量的类型
  34. throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', \gettype($content)));
  35. }
  36. // (string) 会触发__toString方法 如何对象允许的话
  37. $this->content = (string) $content;
  38. return $this;
  39. }

拿到响应后执行return $response->prepare($request);

  1. /**
  2. * Prepares the Response before it is sent to the client.
  3. *
  4. * This method tweaks the Response to ensure that it is
  5. * compliant with RFC 2616. Most of the changes are based on
  6. * the Request that is "associated" with this Response.
  7. *
  8. * @return $this
  9. */
  10. // 总的来说就是设置各种响应头 注意此时并未发送响应
  11. public function prepare(Request $request)
  12. {
  13. $headers = $this->headers;
  14. // 如果是100 204 304系列的状态码 就删除响应数据 删除对应的数据头
  15. if ($this->isInformational() || $this->isEmpty()) {
  16. $this->setContent(null);
  17. $headers->remove('Content-Type');
  18. $headers->remove('Content-Length');
  19. } else {
  20. // Content-type based on the Request
  21. if (!$headers->has('Content-Type')) {
  22. $format = $request->getPreferredFormat();
  23. if (null !== $format && $mimeType = $request->getMimeType($format)) {
  24. $headers->set('Content-Type', $mimeType);
  25. }
  26. }
  27. // Fix Content-Type
  28. $charset = $this->charset ?: 'UTF-8';
  29. if (!$headers->has('Content-Type')) {
  30. $headers->set('Content-Type', 'text/html; charset='.$charset);
  31. } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) {
  32. // add the charset
  33. $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset);
  34. }
  35. // Fix Content-Length
  36. if ($headers->has('Transfer-Encoding')) {
  37. $headers->remove('Content-Length');
  38. }
  39. if ($request->isMethod('HEAD')) {
  40. // cf. RFC2616 14.13
  41. $length = $headers->get('Content-Length');
  42. $this->setContent(null);
  43. if ($length) {
  44. $headers->set('Content-Length', $length);
  45. }
  46. }
  47. }
  48. // Fix protocol
  49. if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) {
  50. $this->setProtocolVersion('1.1');
  51. }
  52. // Check if we need to send extra expire info headers
  53. if ('1.0' == $this->getProtocolVersion() && false !== strpos($headers->get('Cache-Control'), 'no-cache')) {
  54. $headers->set('pragma', 'no-cache');
  55. $headers->set('expires', -1);
  56. }
  57. $this->ensureIEOverSSLCompatibility($request);
  58. if ($request->isSecure()) {
  59. foreach ($headers->getCookies() as $cookie) {
  60. $cookie->setSecureDefault(true);
  61. }
  62. }
  63. return $this;
  64. }
  65. // 至此我们的响应封装好了 等待发送给客户端
  66. // 在发送之前 还要将响应逐步返回
  67. // 值得注意的是 如果你给此路由设置了后置中间件 可能如下
  68. public function handle($request, Closure $next)
  69. {
  70. // 此时拿到的$response就是我们上面响应好了一切 准备发送的响应了 希望你能理解后置中间件的作用了
  71. $response = $next($request);
  72. // header方法位于ResponseTrait
  73. $response->header('Server', 'xy');
  74. return $response;
  75. }

拿到准备好的响应了,逐级向调用栈行层返回,关系如下

  1. 响应返回到Router::runRoute方法
  2. 再返回到Router::dispatchToRoute方法
  3. 再返回到Router::dispatch方法
  4. 再返回到Illuminate\Foundation\Http::sendRequestThroughRouter方法 (注意只要是通过了管道都要注意中间件的类型)
  5. 最终返回到index.php
  6. $response = $kernel->handle(
  7. $request = Illuminate\Http\Request::capture()
  8. );
  9. $response->send();
  10. $kernel->terminate($request, $response);

我们来看send方法 Symfony\Component\HttpFoundation\Response::send

  1. public function send()
  2. {
  3. // 先发送响应头
  4. $this->sendHeaders();
  5. // 再发送响应主体
  6. $this->sendContent();
  7. if (\function_exists('fastcgi_finish_request')) {
  8. fastcgi_finish_request();
  9. } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
  10. static::closeOutputBuffers(0, true);
  11. }
  12. return $this;
  13. }
  14. public function sendHeaders()
  15. {
  16. // headers have already been sent by the developer
  17. if (headers_sent()) {
  18. return $this;
  19. }
  20. // headers
  21. foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
  22. $replace = 0 === strcasecmp($name, 'Content-Type');
  23. foreach ($values as $value) {
  24. // 将之前设置的各种头发送出去
  25. header($name.': '.$value, $replace, $this->statusCode);
  26. }
  27. }
  28. // cookies
  29. foreach ($this->headers->getCookies() as $cookie) {
  30. // 告诉客户端要设置的cookie
  31. header('Set-Cookie: '.$cookie, false, $this->statusCode);
  32. }
  33. // status
  34. // 最后发送个status
  35. header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
  36. return $this;
  37. }
  38. // 发送响应内容
  39. public function sendContent()
  40. {
  41. // 想笑吗 就是这么简单
  42. echo $this->content;
  43. return $this;
  44. }
  45. // 至此真的响应了客户端了

$kernel->terminate($request, $response);

Illuminate\Foundation\Http\Kernel::terminate方法

  1. /**
  2. * Call the terminate method on any terminable middleware.
  3. *
  4. * @param \Illuminate\Http\Request $request
  5. * @param \Illuminate\Http\Response $response
  6. * @return void
  7. */
  8. public function terminate($request, $response)
  9. {
  10. // 调用实现了terminate方法的中间件
  11. $this->terminateMiddleware($request, $response);
  12. // 执行注册的callback
  13. $this->app->terminate();
  14. }

laravel将控制器(闭包)返回的数据封装成response对象

  1. public static function toResponse($request, $response)
  2. {
  3. if ($response instanceof Responsable) {
  4. $response = $response->toResponse($request);
  5. }
  6. if ($response instanceof PsrResponseInterface) {
  7. $response = (new HttpFoundationFactory)->createResponse($response);
  8. } elseif ($response instanceof Model && $response->wasRecentlyCreated) {
  9. $response = new JsonResponse($response, 201);
  10. } elseif (! $response instanceof SymfonyResponse &&
  11. ($response instanceof Arrayable ||
  12. $response instanceof Jsonable ||
  13. $response instanceof ArrayObject ||
  14. $response instanceof JsonSerializable ||
  15. is_array($response))) {
  16. $response = new JsonResponse($response);
  17. } elseif (! $response instanceof SymfonyResponse) {
  18. $response = new Response($response);
  19. }
  20. if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
  21. $response->setNotModified();
  22. }
  23. return $response->prepare($request);
  24. }

观察上面的代码发现:

上面代码的作用是将路由节点返回的数据封装成Response对象等待发送

并且上面的代码存在大量的instanceof判断 (为什么要这样呢 是因为一旦我们从控制器中返回一个实现了

laravel指定接口的实例,laravel就知道该如何渲染这些响应给客户端 此时你可能还不清楚,请看下面的例子)

而且没有else分支(当我们直接返回Resposne实例的时候会直接走到)

并且最终都调用的都是Symfony Response的prepare方法

我们先来看Responsable接口 在laravel中任何一个实现了此接口的对象 都可以响应给客户端

  1. <?php
  2. namespace Illuminate\Contracts\Support;
  3. interface Responsable
  4. {
  5. /**
  6. * Create an HTTP response that represents the object.
  7. *
  8. * @param \Illuminate\Http\Request $request
  9. * @return \Symfony\Component\HttpFoundation\Response
  10. */
  11. // 接收$request参数
  12. // 返回Response对象
  13. public function toResponse($request);
  14. }
  15. // 下面我们在控制器中返回一个实现此接口的实例
  16. // 要实现的逻辑: 接收一个订单id 根据订单状态生成不同的响应,返回给客户端
  17. 1 定义路由
  18. Route::get('yylh/{order}', "\App\Http\Controllers\Debug\TestController@checkStatus");
  19. 2 创建响应
  20. namespace App\Responses;
  21. use App\Models\Order;
  22. use Illuminate\Contracts\Support\Responsable;
  23. use Illuminate\Http\JsonResponse;
  24. class OrderStatusRes implements Responsable
  25. {
  26. protected $status;
  27. public function __construct(Order $order)
  28. {
  29. $this->status = $order->status;
  30. }
  31. public function toResponse($request)
  32. {
  33. if ($this->status) {
  34. // 订单以完成
  35. return new JsonResponse('order completed', 200);
  36. }
  37. // 订单未结算
  38. return view('needToCharge');
  39. }
  40. }
  41. 3 创建控制器
  42. <?php
  43. namespace App\Http\Controllers\Debug;
  44. use App\Http\Controllers\Controller;
  45. use App\Models\Order;
  46. use App\Responses\OrderStatusRes;
  47. class TestController extends Controller
  48. {
  49. public function checkStatus(Order $order)
  50. {
  51. return new OrderStatusRes($order);
  52. }
  53. }
  54. // 进行访问测试
  55. // http://homestead.test/yylh/1
  56. // http://homestead.test/yylh/2
  57. // 可以看到丧心病狂的我们 通过控制器中的一行代码 就实现了根据订单的不同状态回复了不同的响应
  58. // 我想说什么你们应该已经知道了

看toResponse代码 我们发现 只要我们想办法返回符合laravel规定的数据,最终都会被转换成laravel response实例 比如我们可以返回Responsable实例,Arrayable实例,Jsonable实例等等,大家可以尝试直接返回return new Response(),Response::create等等

Route::get('rawReponse', function () {

​ return new Response(range(1,10));

});

更多请查看这位老哥的博客

通过十篇水文,分享了从类的自动加载,到走完laravel的生命周期。

第一次写博客不足太多,不爱看大量代码的道友,可以查看这位外国老哥的博客,其代码较少,但逻辑清晰明了。发现错误,欢迎指导,感谢!!!

collection文档

laravel中使用psr7

Laravel Reponse 响应客户端的更多相关文章

  1. 跟着大彬读源码 - Redis 2 - 服务器如何响应客户端请求?(上)

    上次我们通过问题"启动服务器,程序都干了什么?",跟着源码,深入了解了 Redis 服务器的启动过程. 既然启动了 Redis 服务器,那我们就要连上 Redis 服务干些事情.这 ...

  2. 跟着大彬读源码 - Redis 3 - 服务器如何响应客户端请求?(下)

    继续我们上一节的讨论.服务器启动了,客户端也发送命令了.接下来,就要到服务器"表演"的时刻了. 1 服务器处理 服务器读取到命令请求后,会进行一系列的处理. 1.1 读取命令请求 ...

  3. MVC WebAPI中响应客户端请求返回图片

    // GET api/values public HttpResponseMessage Get() {     Image img = GetImage();     MemoryStream ms ...

  4. laravel常用响应操作

  5. Laravel学习:请求到响应的生命周期

    Laravel请求到响应的整个执行过程,主要可以归纳为四个阶段,即程序启动准备阶段.请求实例化阶段.请求处理阶段.响应发送和程序终止阶段. 程序启动准备阶段 服务容器实例化 服务容器的实例化和基本注册 ...

  6. 利用Socket 实现多客户端的请求与响应

    import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class Serve ...

  7. JSP基础知识➣客户端请求与服务端响应(三)

    JSP客户端请求 浏览器请求服务器端,信息头的一些重要内容,在以后的网络编程中将会经常见到这些信息: Accept:指定浏览器或其他客户端可以处理的MIME类型.它的值通常为 image/png 或 ...

  8. laravel基础课程---8、laravel响应和视图(响应是什么)

    laravel基础课程---8.laravel响应和视图(响应是什么) 一.总结 一句话总结: 就是向请求返回的响应数据(一般为html(视图),当然也可以是变量值):所有的路由及控制器必须返回某个类 ...

  9. [laravel]要点

    1. routing 2. Ioc 3. Facade 4. Artisan 1.routing 参考:http://laravel.com/docs/4.2/routing 了解routing之前先 ...

随机推荐

  1. springMVC请求路径 与实际资源路径关系

    个人理解: 请求路径可以分为两部分:不通过springmvc转发的url:通过springmvc转发的url: 通过特定的配置,告诉springmvc哪些url需要从springmvc处理,处理后再跳 ...

  2. 区间DP 学习笔记

    前言:本人是个DP蒟蒻,一直以来都特别害怕DP,终于鼓起勇气做了几道DP题,发现也没想象中的那么难?(又要被DP大神吊打了呜呜呜. ----------------------- 首先,区间DP是什么 ...

  3. 我是键盘侠-键盘流神器Vimium

    黑客的浏览器. Vimium本着Vim的精神为导航和控制提供键盘快捷键. 注意:谷歌不允许 Vimium在 Chrome Web Store页面和 新选项卡页面上运行.所以按键无效不要惊讶 Vimiu ...

  4. 高级搜索树-伸展树(Splay Tree)

    目录 局部性 双层伸展 查找操作 插入操作 删除操作 性能分析 完整源码 与AVL树一样,伸展树(Splay Tree)也是平衡二叉搜索树的一致,伸展树无需时刻都严格保持整棵树的平衡,也不需要对基本的 ...

  5. .NET或.NET Core Web APi基于tus协议实现断点续传

    前言 前两天我采用技巧式方案基本实现大文件分片上传,这里只是重点在于个人思路和亲身实践,若在实际生产环境要求比较高的话肯定不行,仍存在一些问题需要深入处理,本文继续在之前基础上给出基于tus协议的轮子 ...

  6. 攻防世界-web(进阶)-Training-WWW-Robots

    进行后台扫描,发现一个robots.txt,进入之后说存在fl0g.php,进入即可得flag. cyberpeace{73279bc0d3c28ba6da4d1d3d530e7c16}

  7. Mybatis-05-使用注解开发

    使用注解开发 1 面向接口编程 原因: 解耦.可扩展性.提高复用性 关于接口的理解 定义与实现的分离 两类接口 一个个体的抽象,abstract class 一个个体某个方面的抽象,interface ...

  8. day3 基本语句

         代码缩进为一个tab键  就是四个空格           断点   在代码首行前空白处,双击  然后点右上角臭虫  然后点下面箭头朝下的 1.if 语句  if 判断条件:         ...

  9. Cheese

    题面: 现有一块大奶酪,它的高度为 h,它的长度和宽度我们可以认为是无限大的,奶酪 中间有许多 半径相同 的球形空洞.我们可以在这块奶酪中建立空间坐标系,在坐标系中, 奶酪的下表面为z=0,奶酪的上表 ...

  10. Visual Studio自动编译gRPC工程的设置

    前段时间研究一个java程序,增加一些功能.其中用到java和C#的通信.自然,有多种办法,后来实际上是用javascript调用C#的REST WCF服务实现的.但是在查资料的过程中,发现有个Pro ...