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

TP6实现原理分析系列(二):请求与响应2020-09-14 06:57:45

ad327872b40fb1fc03ae2347208c34cb.png 讲在前面的话:

请求与响应是框架生命周期中两个重要的环节,是框架的收尾两端。请求负责接管客户端请求信息,并对外提供大量接口,获取更精细化数据。响应负责将业务逻辑执行后的结果以各种形式输出。本章节将从九个方面详细介绍请求与响应的实现细节,九小节分别是:请求信息、请求变量、请求类型、头信息、请求缓存、响应类型、响应输出、响应参数、重定向。接下来是详细内容: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是在服务器端执行的脚本语言,与C语言类似,是常用的网站编程语言。PHP独特的语法混合了C、Java、Perl以及 PHP 自创的语法。利于学习,使用广泛,主要适用于Web开发领域。

Tags 标签phpthinkphp6

扩展阅读

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值