深入理解HTTP協議

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命令,輸出請求百度首頁的完整文檔,根據每行前綴,可以分爲四部分:

  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"}
➜  ~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章