HTTP(Hyper Text Transfer Protocol) 超文本传输协议,是基于应用层(TCP/IP参考模型)的通信规范;是从Web服务器传输超文本到客户端的传输协议,无状态的传输协议;不仅能够保证正确、快速、高效的传输超文本文档,而且可以确定资源加载顺序等;在Web开发中,页面缓存控制、数据传递、文档语言参数设定等等,都离不开HTTP协议。HTTP协议是整个Web应用的基础,深入理解HTTP协议,是每个PHP开发工程师必须掌握的知识。

1. 引子:

我们访问一个网页,经历了三个步骤:步骤1. 定位到网页所在服务器;步骤2. 按照一定格式传输到浏览器;步骤3.数据通过浏览器解析展示出来。

这三步流程分别应用到的主要技术:技术1. URL/DNS;技术2. HTTP协议;技术3. HTML/渲染。技术1和3暂时按下不表,步骤2中涉及的HTTP协议,如何构造而成?

我们看一下实例:

1  ➜  ~ curl -v https://www.baidu.com
2  * Rebuilt URL to: https://www.baidu.com/
3  *   Trying 119.75.216.20...
4  * TCP_NODELAY set
5  * Connected to www.baidu.com (119.75.216.20) port 443 (#0)
6  * TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
7  * Server certificate: baidu.com
8  * Server certificate: Symantec Class 3 Secure Server CA - G4
9  * Server certificate: VeriSign Class 3 Public Primary Certification Authority - G5
10 > GET / HTTP/1.1
11 > Host: www.baidu.com
12 > User-Agent: curl/7.54.0
13 > Accept: */*
14 >
15 < HTTP/1.1 200 OK
16 < Accept-Ranges: bytes
17 < Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
18 < Connection: Keep-Alive
19 < Content-Length: 2443
20 < Content-Type: text/html
21 < Date: Fri, 01 Sep 2017 02:22:25 GMT 
22 < Etag: "588603eb-98b"
23 < Last-Modified: Mon, 23 Jan 2017 13:23:55 GMT
24 < Pragma: no-cache
25 < Server: bfe/1.0.8.18
26 < Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
27 <
28 <!DOCTYPE html>
29 <!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc>这里是百度首页的正文</body> </html>
30 * Connection #0 to host www.baidu.com left intact
31 ➜  ~

我们使用curl命令,输出请求百度首页的完整文档,根据每行前缀,可以分为四部分:

  1. * 开头的行,状态行,本文不做详细介绍(其实小编也还没有弄清楚~~~,后续研究)

  2. > 开头的行,是请求报文,常说的请求头

  3. < 开头的行,是响应报文,常说的响应头

  4. 没有前缀的行,line 28~29,是正文部分,要渲染到浏览器中的部分

在这个实例中,请求头、响应头格式内容,包括换行,还有文档具体内容,构成了HTTP协议。可以说,HTTP协议就是传输这些内容格式的规范。

现在我们对HTTP协议应该有了一个大致的概念了吧?具体的构成以及参数,我们下面细说。

 

2. HTTP 协议详解

HTTP 在TCP/IP参考模型中,位于应用层,通常承载于TCP协议之上。如果承载于TLS/SSL之上,就是HTTPS了。HTTP端口默认80,也会使用8080/8000端口;HTTPS端口443。HTTP 协议是以 ASCII 码传输。

2.1 HTTP 协议的特征:
  • HTTP 协议简单,请求一个网页时,只需发送请求方法(GET/POST/…)和资源路径(URI)。

  • HTTP 是无状态协议,本身对事务处理没有记忆能力,但是有专门的技术为HTTP请求提供会话能力:Cookie/Session

  • HTTP 采用问答式交互模型,每次连接只处理一个请求

  • HTTP 是标准的C/S模型

  • HTTP 允许传递数据对象类型丰富,由报头Content-Type标识

2.2 HTTP 协议由请求和响应两部分构成
  • 请求,又由3部分组成:请求行,消息报头,请求正文

  • 响应,也有3部分组成:状态行,消息报头,响应正文

2.3 HTTP请求工作流程
  • Client(通指Browser) 与 Web Server 建立连接

  • Client 发送请求,包括请求行、消息报头、请求正文,示例:GET / HTTP/1.1rnHost: www.baidu.comrn…

  • Server 发送响应,包括状态行、消息报头、响应正文,示例:HTTP/1.1 200 OKrnAccept-Ranges: bytesrn…

  • Client 展示用户数据,Client 与 Server 断开连接

2.4 HTTP 连接详解

在发送HTTP请求头之前,Client 要和 Server 建立连接;连接是传输层的实际环流,建立在两个相互通信的应用程序之间。

2.5 HTTP 请求详解

2.5.1.请求行
格式:Method URI HTTP-Version CRLF,示例:GET / HTTP/1.1rn
参数说明:

2.5.2.请求方法
即我们常用到的GET、POST等等,如下表详解

注意:

安全和幂等的意义在于:当操作没有达到预期的目标时,我们可以不停的重试,而不会对资源产生副作用。从这个意义上说,POST操作往往是有害的,但很多时候我们还是不得不使用它。

POST PUT 创建资源时区别

创建操作可以使用POST,也可以使用PUT,如果URL可以在客户端确定,那么就使用PUT,如果是在服务端确定,那么就使用POST
比如说很多资源使用数据库自增主键作为标识信息,而创建的资源的标识信息只能由服务端提供,这个时候就必须使用POST。

2.5.3.请求正文
在Post/Put 请求中,需要传递数据到服务器,传递的数据就是请求正文部分,和报文部分以空行分隔。如下实例:

1  ➜  ~ curl -v --data-urlencode "name=kevhu.com" "http://127.0.0.1:8080" --trace-ascii /dev/stdout
2  Warning: --trace-ascii overrides an earlier trace/verbose option
3  == Info: Rebuilt URL to: http://127.0.0.1:8080/
4  == Info:   Trying 127.0.0.1...
5  == Info: TCP_NODELAY set
6  == Info: Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
7  => Send header, 148 bytes (0x94)
8  0000: POST / HTTP/1.1
9  0011: Host: 127.0.0.1:8080
10 0027: User-Agent: curl/7.54.0
11 0040: Accept: */*
12 004d: Content-Length: 14
13 0061: Content-Type: application/x-www-form-urlencoded
14 0092:
15 => Send data, 14 bytes (0xe)
16 0000: name=kevhu.com
17 == Info: upload completely sent off: 14 out of 14 bytes
18 <= Recv header, 17 bytes (0x11)
19 0000: HTTP/1.1 200 OK
20 <= Recv header, 22 bytes (0x16)
21 0000: Host: 127.0.0.1:8080
22 <= Recv header, 19 bytes (0x13)
23 0000: Connection: close
24 <= Recv header, 26 bytes (0x1a)
25 0000: X-Powered-By: PHP/7.0.17
26 <= Recv header, 40 bytes (0x28)
27 0000: Content-type: text/html; charset=UTF-8
28 <= Recv header, 2 bytes (0x2)
29 0000:
30 <= Recv data, 34 bytes (0x22)
31 0000: Array.(.    [name] => kevhu.com.).
32 Array
33 (
34     [name] => kevhu.com
35 )
36 == Info: Closing connection 0

说明:

  • ==行表示连接状态;=>发送请求报文的提示;<=接收响应报文的提示;其他行表示传递报文

  • line 8~13: 请求行,和请求消息报文部分

  • line 14: 空行,分隔报文和请求正文

  • line 16: 请求正文部分,POST 请求提交给服务器的数据

2.6 HTTP 响应详解

2.6.1 状态行

格式:HTTP-Version Status-Code Reason-Phrase CRLF
示例:HTTP/1.1 200 OK rn
参数说明:

2.6.2 状态码

常见有五种响应,由状态码的第一位数字标识出来:
常见HTTP Code 说明

2.6.3 响应正文

参考code 2,来说明响应正文:

  • line 29:空行,分隔响应报头和响应正文

  • line 31:响应的正文部分

2.7 HTTP 核心消息报头详解

2.7.1 格式

  • 格式:Name: Value

  • 示例:Content-type: text/html; charset=UTF-8

2.7.2 HTTP消息报头通常分为四类:

  • 普通报头:有少数报头域,同时可以用于请求和响应消息,如缓存控制、连接控制

  • 请求报头:请求的附加信息以及客户端自身信息,如UA、Accept

  • 响应报头:服务器发回不能放到状态行的附加响应信息

  • 实体报头:定义了关于实体正文和请求所标记的资源的元信息,如:无实体正文

2.7.3 重要报头的说明

3. PHP HTTP 请求实例
实例演示HTTP 协议请求一个资源

首先准备一个Server文件,并启动一个PHP Web 服务:

➜  ~ more index.php
<?php
if (!empty($_POST)) echo "Response Body POST: ", json_encode($_POST), "\n";
if (!empty($_GET)) echo "Response Body GET: ", json_encode($_GET), "\n";
➜  ~ php -S 127.0.0.1:8080
PHP 7.0.17 Development Server started at Sun Sep  3 12:59:52 2017
Listening on http://127.0.0.1:8080
Document root is /Users/kevhu
Press Ctrl-C to quit.

然后,我们在准备一个GET请求文件,client_get.php,并执行查看结果:

1  ➜  ~ more client_get.php
2  <?php
3  $host = "127.0.0.1";
4  $query = "name=kev&code=php";
5  $port = 8080;
6  $fp = fsockopen($host, $port, $errno, $errstr, 30);
7  if (!$fp) {
8      echo $errstr ($errno), "\n";
9  } else {
10     $out = "GET /?$query HTTP/1.1\r\n";
11     $out .= "Host: $host\r\n";
12     $out .= "Connection: Close\r\n";
13     $out .= "\r\n";
14    fwrite($fp, $out);
15     while(!feof($fp)){
16         echo fgets($fp, 128);
17     }
18     fclose($fp);
19 }
20 ➜  ~ php client_get.php
21 HTTP/1.1 200 OK
22 Host: 127.0.0.1
23 Connection: close
24 X-Powered-By: PHP/7.0.17
25 Content-type: text/html; charset=UTF-8
26  
27 Response Body GET: {"name":"kev","code":"php"}
28 ➜  ~

Code 4 代码说明

  • line 10~13:是编写GET请求的报文

  • line 21~27:是服务器响应的报文信息,以及正文部分 ‘Response Body GET: {"name":"kev","code":"php"}’

最后,我们再准备一个POST请求脚本,client_post.php,并执行查看结果:

1  ➜  ~ more client_post.php
2  <?php
3  $host = "127.0.0.1";
4  $body = "name=kev&code=php";
5  $len = strlen($body);
6  $port = 8080;
7  $fp = fsockopen($host, $port, $errno, $errstr, 30);
8  if (!$fp) {
9      echo $errstr ($errno), "\n";
10 } else {
11     $out = "POST / HTTP/1.1\r\n";
12     $out .= "Host: $host\r\n";
13     $out .= "Connection: Close\r\n";
14     $out .= "Content-Type: application/x-www-form-urlencoded\r\n";
15     $out .= "Content-Length: $len\r\n";
16     $out .= "\r\n";
17     $out .= "$body\r\n";
18     fwrite($fp, $out);
19     while(!feof($fp)){
20         echo fgets($fp, 128);
21     }
22     fclose($fp);
23 }
24 ➜  ~ php client_post.php
25 HTTP/1.1 200 OK
26 Host: 127.0.0.1
27 Connection: close
28 X-Powered-By: PHP/7.0.17
29 Content-type: text/html; charset=UTF-8
30  
31 Response Body POST: {"name":"kev","code":"php"}
32 ➜  ~

code 5 代码说明:

  • line 11~17 POST的请求报文信息,以及提交数据正文部分

  • line 25~31 Web服务器响应的报文信息,以及响应的正文

4. PHP中与HTTP相关的函数/变量

实例展示:

PHP查看响应报文:

➜  ~ more client_head.php
<?php
$host = "127.0.0.1";
$port = 8080;
$ht = file_get_contents("http://$host:$port");
echo "http_response_header=", json_encode($http_response_header), "\n";
echo "\n";
$fp = fopen("http://$host:$port", "r");
$head = stream_get_meta_data($fp);
echo 'stream_get_meta_data($fp) = ', json_encode($head), "\n";
➜  ~ php client_head.php
http_response_header=["HTTP\/1.0 200 OK","Host: 127.0.0.1:8080","Connection: close","X-Powered-By: PHP\/7.0.17","Content-type: text\/html; charset=UTF-8"]

stream_get_meta_data($fp) = {"timed_out":false,"blocked":true,"eof":false,"wrapper_data":["HTTP\/1.0 200 OK","Host: 127.0.0.1:8080","Connection: close","X-Powered-By: PHP\/7.0.17","Content-type: text\/html; charset=UTF-8"],"wrapper_type":"http","stream_type":"tcp_socket\/ssl","mode":"r","unread_bytes":0,"seekable":false,"uri":"http:\/\/127.0.0.1:8080"}
➜  ~

使用context实现HTTP请求

➜  ~ more client_context.php
<?php
$host = "127.0.0.1";
$body = "name=kev&code=php";
$len = strlen($body);
$port = 8080;
$opts = [
    "http" => [
        "method" => "POST",
        "header" => "Host: $host\r\n"
            . "Connection: Close\r\n"
            . "Content-Type: application/x-www-form-urlencoded\r\n"
            . "Content-Length: $len\r\n"
            . "\r\n" . "$body\r\n"
    ]
];

$context = stream_context_create($opts);

$fp = fopen("http://$host:$port", 'r', false, $context);
fpassthru($fp);
fclose($fp);
➜  ~ php client_context.php
Response Body POST: {"name":"kev","code":"php"}
➜  ~

PHP中级】深入理解HTTP协议_HTTP