HTTP(Hyper Text Transfer Protocol) 超文本傳輸協議,是基於應用層(TCP/IP參考模型)的通信規範;是從Web服務器傳輸超文本到客戶端的傳輸協議,無狀態的傳輸協議;不僅能夠保證正確、快速、高效的傳輸超文本文檔,而且可以確定資源加載順序等;在Web開發中,頁面緩存控制、數據傳遞、文檔語言參數設定等等,都離不開HTTP協議。HTTP協議是整個Web應用的基礎,深入理解HTTP協議,是每個coder開發工程師必須掌握的知識。 https://mp.weixin.qq.com/s/XixbbCjpancZmXwrv9vZjg
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命令,輸出請求百度首頁的完整文檔,根據每行前綴,可以分爲四部分:
-
* 開頭的行,狀態行,本文不做詳細介紹(其實小編也還沒有弄清楚~~~,後續研究)
-
> 開頭的行,是請求報文,常說的請求頭
-
< 開頭的行,是響應報文,常說的響應頭
-
沒有前綴的行,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"}
➜ ~