TP6实现原理分析系列(二):请求与响应

码农天地 -
TP6实现原理分析系列(二):请求与响应
讲在前面的话:

请求与响应是框架生命周期中两个重要的环节,是框架的收尾两端。请求负责接管客户端请求信息,并对外提供大量接口,获取更精细化数据。响应负责将业务逻辑执行后的结果以各种形式输出。本章节将从九个方面详细介绍请求与响应的实现细节,九小节分别是:请求信息、请求变量、请求类型、头信息、请求缓存、响应类型、响应输出、响应参数、重定向。接下来是详细内容:

2.1 请求信息:

什么是请求信息?请求信息是指客户端请求服务端,发送过来的所有信息,这些信息包括请求协议、域名、端口、请求方法、请求参数等等,对于这些信息PHP语言将它们存储在一些超全局数组中,所谓的请求对象接管请求信息就是接管这些超全局数组,请看具体代码:



1.  // 这是 Request 类的初始化方法,接管了除$_SESSION之外的超全局数据
    
2.  // vendortopthinkframeworksrcthinkRequest.php
    
3.  public static function __make(App $app)
    
4.  {
    
5.          $request = new static();
    

7.          if (function_exists('apache_request_headers') && $result = apache_request_headers()) {
    
8.              $header = $result;
    
9.          } else {
    
10.              $header = [];
    
11.              $server = $_SERVER;
    
12.              foreach ($server as $key => $val) {
    
13.                  if (0 === strpos($key, 'HTTP_')) {
    
14.                      $key          = str_replace('_', '-', strtolower(substr($key, 5)));
    
15.                      $header[$key] = $val;
    
16.                  }
    
17.              }
    
18.              if (isset($server['CONTENT_TYPE'])) {
    
19.                  $header['content-type'] = $server['CONTENT_TYPE'];
    
20.              }
    
21.              if (isset($server['CONTENT_LENGTH'])) {
    
22.                  $header['content-length'] = $server['CONTENT_LENGTH'];
    
23.              }
    
24.          }
    

26.          $request->header = array_change_key_case($header);
    
27.          $request->server = $_SERVER;
    
28.          $request->env    = $app->env;
    

30.          $inputData = $request->getInputData($request->input);
    

32.          $request->get     = $_GET;
    
33.          $request->post    = $_POST ?: $inputData;
    
34.          $request->put     = $inputData;
    
35.          $request->request = $_REQUEST;
    
36.          $request->cookie  = $_COOKIE;
    
37.          $request->file    = $_FILES ?? [];
    

39.          return $request;
    
40.  }
    

接管了这一系列信息之后,Request 类通过各种精细化接口对这些信息的部分获取全部进行输出,具体对外提供了那些接口,大家可以参考手册,这里对几个重要的接口进行分析。

当前访问域名或者IP:host() 方法 



1.  // 获取访问域名 vendortopthinkframeworksrcthinkRequest.php  1754 行
    
2.  public function host(bool $strict = false): string
    
3.  {
    
4.          if ($this->host) {
    
5.              $host = $this->host;
    
6.          } else {
    

8.              // 通过 $_SERVER 中的HTTP_X_FORWARDED_HOST属性或者HTTP_HOST来获取
    
9.              $host = strval($this->server('HTTP_X_FORWARDED_HOST') ?: $this->server('HTTP_HOST'));
    
10.          }
    
11.          // 这里是判断要不要包含端口
    
12.          return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host;
    
13.  }
    

当前完整URL:url() 方法



1.  // 获取当前完整URL 包括QUERY_STRING 
    
2.  // vendortopthinkframeworksrcthinkRequest.php  460 行
    
3.  public function url(bool $complete = false): string
    
4.  {
    
5.          if ($this->url) {
    
6.              $url = $this->url;
    
7.          } elseif ($this->server('HTTP_X_REWRITE_URL')) {
    
8.              $url = $this->server('HTTP_X_REWRITE_URL');
    
9.          } elseif ($this->server('REQUEST_URI')) {
    
10.              $url = $this->server('REQUEST_URI');
    
11.          } elseif ($this->server('ORIG_PATH_INFO')) {  // web服务器重定向会出现这个属性
    
12.              $url = $this->server('ORIG_PATH_INFO') . (!empty($this->server('QUERY_STRING')) ? '?' . $this->server('QUERY_STRING') : '');
    
13.          } elseif (isset($_SERVER['argv'][1])) {
    
14.              $url = $_SERVER['argv'][1];
    
15.          } else {
    
16.              $url = '';
    
17.          }
    

19.          return $complete ? $this->domain() . $url : $url;
    
20.  }
    

2.2 输入变量:

输入变量是指可以变化的输入信息,那什么是可以变化的输入信息呢?比如,查询参数、上传文件、Post请求体这些都是可以变化的,用户都是通过这些可以变化的东西去向服务器获取或者保存信息,比如通过查询参数的变化去获取不同的商品信息,通过提交图片信息保存自己的照片,这些对于框架而言都是输入变量,Request(请求) 类提供了丰富的API帮我们去获取这些信息,请看详细代码:

获取 $_POST 变量: post() 方法



1.  // 获取POST参数,传递属性名称就可以获取它对应的值
    
2.  // vendortopthinkframeworksrcthinkRequest.php  961 行
    
3.  public function post($name = '', $default = null, $filter = '')
    
4.  {
    
5.          if (is_array($name)) {
    
6.              return $this->only($name, $this->post, $filter);
    
7.          }
    

9.          return $this->input($this->post, $name, $default, $filter);
    
10.  }
    

13.  // 获取变量,并且支持过滤 不管是get() post() 还是 param() 都依赖这个方法
    
14.  // vendortopthinkframeworksrcthinkRequest.php 1241行
    
15.  public function input(array $data = [], $name = '', $default = null, $filter = '')
    
16.  {
    
17.          if (false === $name) {
    
18.              // 获取原始数据
    
19.              return $data;
    
20.          }
    

22.          $name = (string) $name;
    
23.          if ('' != $name) {
    
24.              // 解析name
    
25.              if (strpos($name, '/')) {
    
26.                  [$name, $type] = explode('/', $name);
    
27.              }
    

29.              $data = $this->getData($data, $name);
    

31.              if (is_null($data)) {
    
32.                  return $default;
    
33.              }
    

35.              if (is_object($data)) {
    
36.                  return $data;
    
37.              }
    
38.          }
    

40.          $data = $this->filterData($data, $filter, $name, $default);
    

42.          if (isset($type) && $data !== $default) {
    
43.              // 强制类型转换
    
44.              $this->typeCast($data, $type);
    
45.          }
    

47.          return $data;
    
48.   }
    

获取 $_FILES 变量(上传文件信息):file() 方法



1.  // 获取上传文件的信息,这里返回的是一个对象数组,每一个上传文件都会生成一个上传对象
    
2.  // vendortopthinkframeworksrcthinkRequest.php 1128 行
    
3.  public function file(string $name = '')
    
4.  {
    
5.          $files = $this->file; //
    
6.          if (!empty($files)) {
    

8.              if (strpos($name, '.')) {
    
9.                  [$name, $sub] = explode('.', $name);
    
10.              }
    

12.              // 处理上传文件
    
13.              $array = $this->dealUploadFile($files, $name);
    

15.              if ('' === $name) {
    
16.                  // 获取全部文件
    
17.                  return $array;
    
18.              } elseif (isset($sub) && isset($array[$name][$sub])) {
    
19.                  return $array[$name][$sub];
    
20.              } elseif (isset($array[$name])) {
    
21.                  return $array[$name];
    
22.              }
    
23.          }
    
24.  }
    

26.  // 上传文件对象创建 vendortopthinkframeworksrcthinkRequest.php 1175 行
    
27.  $item[] = new UploadedFile($temp['tmp_name'], $temp['name'], $temp['type'], $temp['error']);
    

29.  // 文件上传依赖 flysystem 组件这个我们后面再介绍
    

2.3 请求类型:

请求类型是指 HTTP 请求类型,HTTP 总共五种请求类型,分别是:GET、POST、PUT、 DELETE、 HEAD。Request 类为我们提供的接口分为两类,一类是获取当前请求类型,一类是判断是否是某种类型,接下为大家分析两个代表性API:

获取当前请求类型: method() 方法



1.  // 获取请求类型 请求类型的获取支持类型伪造,如果伪造请求类型优先获取伪造类型
    
2.  // vendortopthinkframeworksrcthinkRequest.php  717 行
    
3.  public function method(bool $origin = false): string
    
4.  {
    
5.          if ($origin) {
    
6.              // 获取原始请求类型
    
7.              return $this->server('REQUEST_METHOD') ?: 'GET';
    
8.          } elseif (!$this->method) {
    
9.              if (isset($this->post[$this->varMethod])) {
    
10.                  $method = strtolower($this->post[$this->varMethod]);
    
11.                  if (in_array($method, ['get', 'post', 'put', 'patch', 'delete'])) {
    
12.                      $this->method    = strtoupper($method);
    
13.                      $this->{$method} = $this->post;
    
14.                  } else {
    
15.                      $this->method = 'POST';
    
16.                  }
    
17.                  unset($this->post[$this->varMethod]);
    
18.              } elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) {
    
19.                  $this->method = strtoupper($this->server('HTTP_X_HTTP_METHOD_OVERRIDE'));
    
20.              } else {
    
21.                  $this->method = $this->server('REQUEST_METHOD') ?: 'GET';
    
22.              }
    
23.          }
    

25.          return $this->method;
    
26.  }
    

  判断是否AJAX请求: isAjax() 方法 



1.  // 判断是否为 ajax 请求 主要是通过 HTTP_X_REQUESTED_WITH 属性来判断,只有ajax请求才会有这个属性
    
2.  // ajax 请求同样支持类型伪造,并且有限获取伪造类型。
    
3.  // vendortopthinkframeworksrcthinkRequest.php  1545 行
    
4.  public function isAjax(bool $ajax = false): bool
    
5.  {
    
6.          $value  = $this->server('HTTP_X_REQUESTED_WITH');
    
7.          $result = $value && 'xmlhttprequest' == strtolower($value) ? true : false;
    

9.          if (true === $ajax) {
    
10.              return $result;
    
11.          }
    

13.          return $this->param($this->varAjax) ? true : $result;
    
14.  }
    

2.4 请求头信息:

请求头信息就是指 HTTP 请求报文头,以 “属性名:属性值”的形式组织信息,服务端据此获取绝大部分客户端信息。Request 以两种方式获取此信息,第一种是从 $_SERVER 超全局数组中提取,第二种是 从 apache_request_headers这个方法中获取(前提是要采用Apache作为web容器),看代码:



1.  // 在初始化方法中获取 vendortopthinkframeworksrcthinkRequest.php 307 行
    
2.  public static function __make(App $app)
    
3.  {
    
4.          $request = new static();
    

6.          // 只有在Apache 容器下才会有此方法
    
7.          if (function_exists('apache_request_headers') && $result = apache_request_headers()) {
    
8.              $header = $result;
    
9.          } else {
    
10.              // 没有的话从 $_SERVER 中提取
    
11.              $header = [];
    
12.              $server = $_SERVER;
    
13.              foreach ($server as $key => $val) {
    
14.                  if (0 === strpos($key, 'HTTP_')) {
    
15.                      $key          = str_replace('_', '-', strtolower(substr($key, 5)));
    
16.                      $header[$key] = $val;
    
17.                  }
    
18.              }
    
19.              if (isset($server['CONTENT_TYPE'])) {
    
20.                  $header['content-type'] = $server['CONTENT_TYPE'];
    
21.              }
    
22.              if (isset($server['CONTENT_LENGTH'])) {
    
23.                  $header['content-length'] = $server['CONTENT_LENGTH'];
    
24.              }
    
25.          }
    
26.  }
    

请求信息可以通过 header 方法获取:



1.  // 设置或者获取请求头 vendortopthinkframeworksrcthinkRequest.php 1221 行
    
2.  public function header(string $name = '', string $default = null)
    
3.  {
    
4.          if ('' === $name) {
    
5.              return $this->header;
    
6.          }
    

8.          $name = str_replace('_', '-', strtolower($name));
    

10.          return $this->header[$name] ?? $default;
    
11.  }
    

2.5 请求缓存:

什么是请求缓存?就是将请求的内容缓存在客户端,下次请求的服务端,服务端只需响应状态码,无需响应内容,浏览器自动从缓存中读取,这样能大大提升用户体验。这么好的功能是如何实现的呢?其实是通过全局中间件实现,那如何开启呢?看代码:



1.  <?php
    
2.  // 第一步:将全局缓存中间件的注释去掉
    
3.  // 全局中间件定义文件 appmiddleware.php
    
4.  return [
    
5.      // 全局请求缓存
    
6.       thinkmiddlewareCheckRequestCache::class,
    
7.      // 多语言加载
    
8.       thinkmiddlewareLoadLangPack::class,
    
9.      // Session初始化
    
10.       thinkmiddlewareSessionInit::class
    
11.  ];
    

13.  // 第二步:将 路由配置中此项设置为 true
    
14.  // configroute.php  30 行
    

16.      // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则
    
17.      'request_cache_key'     => true,
    

  接下来揭晓实现原理,代码如下:

 1.      // 中间件入口函数 且只支持 get 请求
    
2.     public function handle($request, Closure $next, $cache = null)
    
3.     {
    
4.          if ($request->isGet() && false !== $cache) {
    
5.              $cache = $cache ?: $this->getRequestCache($request);
    

7.              if ($cache) {
    
8.                  if (is_array($cache)) {
    
9.                      [$key, $expire, $tag] = $cache;
    
10.                  } else {
    
11.                      $key    = str_replace('|', '/', $request->url());
    
12.                      $expire = $cache;
    
13.                      $tag    = null;
    
14.                  }
    

16.                  if (strtotime($request->server('HTTP_IF_MODIFIED_SINCE', '')) + $expire > $request->server('REQUEST_TIME')) {
    
17.                      // 读取缓存
    
18.                      return Response::create()->code(304);
    
19.                  } elseif (($hit = $this->cache->get($key)) !== null) {
    
20.                      [$content, $header, $when] = $hit;
    
21.                      if (null === $expire || $when + $expire > $request->server('REQUEST_TIME')) {
    
22.                          return Response::create($content)->header($header);
    
23.                      }
    
24.                  }
    
25.              }
    
26.          }
    

28.          $response = $next($request);
    

30.          if (isset($key) && 200 == $response->getCode() && $response->isAllowCache()) {
    
31.              $header                  = $response->getHeader();
    
32.              $header['Cache-Control'] = 'max-age=' . $expire . ',must-revalidate';
    
33.              $header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT';
    
34.              $header['Expires']       = gmdate('D, d M Y H:i:s', time() + $expire) . ' GMT';
    

36.              $this->cache->tag($tag)->set($key, [$response->getContent(), $header, time()], $expire);
    
37.          }
    

39.          return $response;
    
40.      }
2.6 响应类型:

响应类型是服务端响应客户端内容的形式,框架实现七种响应类型分别是 File、Html、Json、Jsonp、Redirect、View、Xml。其实就是七个响应类,都继承 Response 类,并且重写了部分父类的方法,下图为七个类定义的位置:

      接下来分别介绍一下这个七个响应类所代表的含义:

      File : 通过修改响应头信息,实现文件下载。

     Html: 就是响应 html 页面,这是绝大数响应形式。

    Json: 响应json数据,用于 API 响应。

    Jsonp: 这是跨域请求响应。

    Redirect : 这是重定向。

    View: 这是响应视图本质还是html页面,这样做的好处就是无需调用视图里面 fetch 方法也可以渲染模板。

     Xml:这是响应 xml 数据。

2.7 响应输出:

响应输出其实在生命周期里面已经介绍过了,这里再补充几个点。响应输出就是调用 Response 的 send 方法,在这个方法里面有一个重要的操作就是获取输出数据,看代码:



1.  public function send(): void
    
2.  {
    
3.          // 处理输出数据
    
4.          $data = $this->getContent();
    

6.          // 省略若干代码
    
7.  }
    

9.  public function getContent(): string
    
10.  {
    
11.          if (null == $this->content) {
    
12.              $content = $this->output($this->data);
    

14.              if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([
    
15.                  $content,
    
16.                  '__toString',
    
17.              ])
    
18.              ) {
    
19.                  throw new InvalidArgumentException(sprintf('variable type error: %s', gettype($content)));
    
20.              }
    

22.              $this->content = (string) $content;
    
23.          }
    

25.          return $this->content;
    
26.  }
    

output 方法,在除 Html 之外的六个类中都有被重写,这个方法决定输出内容的差异,这个给大家看一下 File 类的 output 方法。代码如下:



1.  // 此代码实现了一个文件下载功能 vendortopthinkframeworksrcthinkresponseFile.php
    
2.  protected function output($data)
    
3.  {
    
4.          if (!$this->isContent && !is_file($data)) {
    
5.              throw new Exception('file not exists:' . $data);
    
6.          }
    

8.          ob_end_clean();
    

10.          if (!empty($this->name)) {
    
11.              $name = $this->name;
    
12.          } else {
    
13.              $name = !$this->isContent ? pathinfo($data, PATHINFO_BASENAME) : '';
    
14.          }
    

16.          if ($this->isContent) {
    
17.              $mimeType = $this->mimeType;
    
18.              $size     = strlen($data);
    
19.          } else {
    
20.              $mimeType = $this->getMimeType($data);
    
21.              $size     = filesize($data);
    
22.          }
    

24.          $this->header['Pragma']                    = 'public';
    
25.          $this->header['Content-Type']              = $mimeType ?: 'application/octet-stream';
    
26.          $this->header['Cache-control']             = 'max-age=' . $this->expire;
    
27.          $this->header['Content-Disposition']       = ($this->force ? 'attachment; ' : '') . 'filename="' . $name . '"';
    
28.          $this->header['Content-Length']            = $size;
    
29.          $this->header['Content-Transfer-Encoding'] = 'binary';
    
30.          $this->header['Expires']                   = gmdate("D, d M Y H:i:s", time() + $this->expire) . ' GMT';
    

32.          $this->lastModified(gmdate('D, d M Y H:i:s', time()) . ' GMT');
    

34.          return $this->isContent ? $data : file_get_contents($data);
    
35.  }
    

2.8 响应参数:

所谓的响应参数就是指响应内容,状态码,响应头。虽然这些信息可以统一设置,同时 Response 也提供了单独的方法就设置这些内容,看代码演示:

响应内容



1.  // 输出数据设置
    
2.  public function data($data)
    
3.  {
    
4.          $this->data = $data;
    

6.          return $this;
    
7.  }
    

状态码



1.  // 设置 HTTP 状态码
    
2.  public function code(int $code)
    
3.  {
    
4.          $this->code = $code;
    

6.          return $this;
    
7.  }
    

响应头



1.  // 设置响应头
    
2.  public function header(array $header = [])
    
3.  {
    
4.          $this->header = array_merge($this->header, $header);
    

6.          return $this;
    
7.  }
    

2.9 重定向:

重定向是属于响应类型的一种,这里就不多做介绍了,直接看框架的实现代码:



1.  // 实现就是通过 location实现的 这里的 $data 为 url
    
2.  protected function output($data): string
    
3.  {
    
4.          $this->header['Location'] = $data;
    

6.          return '';
    
7.  }
    

各位读者,关于请求与响应的介绍到这里就结束了,感谢大家的阅读,如果对于文字教程意犹未尽,可以移步下行地址观看视频教程,并且可以本人一对一交流。

教程地址:https://edu.csdn.net/course/detail/28045

特别申明:本文内容来源网络,版权归原作者所有,如有侵权请立即与我们联系(cy198701067573@163.com),我们将及时处理。

php介绍

PHP即“超文本预处理器”,是一种通用开源脚本语言。PHP是在服务器端执行的脚本语言,与C语言类似,是常用的网站编程语言。PHP独特的语法混合了C、Java、Perl以及 PHP 自创的语法。利于学习,使用广泛,主要适用于Web开发领域。

Tags 标签

phpthinkphp6

扩展阅读

加个好友,技术交流

1628738909466805.jpg