深入剖析 Http 协议
HTTP 顾名思义就是一个协议,但是要从百科上看有点让初学者摸不着头脑,太枯燥,并且也不好结合实际情形去理解。这里用一些小小的事例,争取让大家对HTTP协议有个基本的认知。
1. 首先看一个PHp手册上的示例:
<?php $fp = fsockopen("www.example.com", 80, $errno, $errstr, 30); if (!$fp) { echo "$errstr ($errno)<br />\n"; } else { $out = "GET / HTTP/1.1\r\n"; $out .= "Host: www.example.com\r\n"; $out .= "Connection: Close\r\n\r\n"; fwrite($fp, $out); while (!feof($fp)) { echo fgets($fp, 128); } fclose($fp); } ?>
运行结果是这样的
HTTP/1.1 200 OK Cache-Control: max-age=604800 Content-Type: text/html Date: Sun, 29 May 2016 03:33:38 GMT Etag: "359670651+gzip+ident" Expires: Sun, 05 Jun 2016 03:33:38 GMT Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT Server: ECS (iad/182A) Vary: Accept-Encoding X-Cache: HIT x-ec-custom-error: 1 Content-Length: 1270 Connection: close <!doctype html> <html> ........ ........ </html>
上面这个例子是PHP程序模拟普通用户发起一个到 www.example.com 的访问请求。
看的出来请求头各个字段间有一个 \r\n ,这就是协议中其中一小部分。虽然在运行结果中看不到 \r\n ,但是能看到响应头各字段之前使用换行隔开,响应头与响应主体之间有一个空行隔开,这其中都是 \r\n 。空行本省也就是一个 \r\n 的组合 。\r\n 有个专业术语叫做 CRLF,如果看到这4个字母就把它理解为 \r\n 就好。
对于其中的各个请求字段、响应字段都是什么含义,请自行百度,这类型内容比较多,在这里就略过了。其中HTTP基本工作原理也可以参考文章结尾处“HTTP响应报文与工作原理详解”章节,写的比较详细。
2. 接着再看一个post 请求
<?php $host = 'log.bbs.le.com'; $port = 80; $fp = fsockopen($host, $port, $error_no, $error_desc, 30); if ($fp) { $path = '/app/error?a=123456'; $data = 'ac=1&bc=2'; $http_entity_body = $data; $http_entity_length = strlen($http_entity_body); $http_entity_type = 'application/x-www-form-urlencoded'; $out = ''; $out .= "POST {$path} HTTP/1.1\r\n"; $out .= "Host: {$host}\r\n"; $out .= "Content-Type: {$http_entity_type}\r\n"; $out .= "Content-Length: {$http_entity_length}\r\n"; $out .= "Connection: close\r\n\r\n"; $out .= $http_entity_body . "\r\n"; fputs($fp, $out); $d = ''; while (!feof($fp)) { $t = fgets($fp, 4096); $d .= $t; } fclose($fp); echo $d; } ?>
运行结果是这样的
HTTP/1.1 200 OK Server: nginx Date: Sun, 29 May 2016 05:13:35 GMT Content-Type: text/html Transfer-Encoding: chunked Connection: close Vary: Accept-Encoding X-Powered-By: PHP/5.5.10 2 ok 0
与第一个例子中的GET请求对比,这里有几个不一样的地方:
1. 请求头的第一行 ,从 GET 变为了 POST
2. 请求头中加入了 Content-Type ,用来标识数据类型,不然服务端 $_POST 将不解析这个data数据,也就获取不到对应值
3. 请求头中加入了 Content-Length ,用户标识数据长度,这里标识POST传递参数及值的总字符长度,这个如果是POST请求也是必传参数,不然发送到服务端的数据会被截断,得不到预期的结果
4. 请求实体追加在请求头的后面,多了一个空行 \r\n
5. 响应头来自于服务器配置,所以暂时不做定论
6. 响应主体多了 2 0 ,并且与真正的服务端返回数据用换行隔开
第 1-4 条都是http协议的组成部分,也就是说你必须这么做,不然会出现异常的。
第 6 条是什么意思,为什么会多出 2 0 两个字符,经过再三确认,发现这个数字与响应主体长度相关。刚开始认为是不同服务器配置差异导致的,那么我们再模拟一下GET 分别请求动态资源与静态资源试试
<?php $host = 'log.bbs.le.com'; $port = 80; $fp = fsockopen($host, $port, $error_no, $error_desc, 30); if ($fp) { // $path = '/app/error?a=123456'; $path = '/test.html'; $out = ''; $out .= "GET {$path} HTTP/1.1\r\n"; // $out .= "GET {$path} HTTP/1.0\r\n"; $out .= "Host: {$host}\r\n"; $out .= "Connection: close\r\n\r\n"; fputs($fp, $out); $d = ''; while (!feof($fp)) { $t = fgets($fp, 4096); $d .= $t; } fclose($fp); echo $d; } ?>
得出的结论是:
GET 请求动态资源同样会加上 2 0 ,而请求静态资源则如同上方的 example.com 的结果一致,那么说明这个 2 0 与动态资源有关。 最终确定这个与Transfer-Encoding: chunked 有直接关系,这表明响应内容按一块一块输出,可以提高响应速度。为了验证这个结果,我们在nginx.conf 的http 段加入以下配置,并重载配置,发现输出结果正常了,没有 2 0 这样的字符了。
chunked_transfer_encoding off;运行结果是
HTTP/1.1 200 OK Server: nginx Date: Sun, 29 May 2016 06:11:22 GMT Content-Type: text/html Connection: close Vary: Accept-Encoding X-Powered-By: PHP/5.5.10 ok
为什么要对 2 0 这些字符那么在意呢?
因为我们在调用服务端接口是,要获取到响应结果,但是你发现这些字符不是固定的,很是碍事。但是上面的分析只是找到问题发生的原因,基于性能的考虑,transfer-encoding: chunked 还是要开启的。一种解决办法是:把上方的请求头中的 HTTP/1.1 换为 HTTP/1.0,另一种解决办法是推荐使用CURL库来调用服务端接口,这样获取到的结果在 CURL 内部会转换,得到的结果就没有 2 0 这些数字了。
相关资料及文献:
HTTP响应报文与工作原理详解 http://network.chinabyte.com/401/13238901.shtml
transfer-encoding:chunked的含义 http://blog.csdn.net/whatday/article/details/7571451
nginx配置关闭chunked http://www.6san.com/759/
HTTP 1.1与HTTP 1.0的比较 http://blog.csdn.net/elifefly/article/details/3964766