HTTP/0.9
HTTP協議的最初版本,功能簡陋,僅支持請求方式GET,並且僅能請求訪問HTML格式的資源。
HTTP/1.0
請求行必須在尾部添加協議版本字段(http/1.0);必須包含頭消息
在0.9版本上做了進步,增加了請求方式POST和HEAD;不再局限於0.9版本的HTML格式,根據Content-Type可以支持多種數據格式,即MIME多用途互聯網郵件擴展,例如text/html、image/jpeg等;同時也開始支持cache,就是當客戶端在規定時間內訪問統一網站,直接訪問cache即可。
再次,HTTP請求和回應的格式也變了。除了數據部分,每次通信都必須包括頭信息(HTTP header),用來描述一些元數據。
其他的新增功能還包括狀態碼(status code)、多字符集支持、多部分發送(multi-part type)、權限(authorization)、緩存(cache)、內容編碼(content encoding)等。
但是1.0版本的工作方式是每次TCP連接只能發送一個請求,當服務器響應后就會關閉這次連接,下一個請求需要再次建立TCP連接,就是不支持keepalive。
TCP連接的新建成本很高,因為需要客戶端和服務器三次握手,並且開始時發送速率較慢(slow start)。所以,HTTP 1.0版本的性能比較差。隨着網頁加載的外部資源越來越多,這個問題就愈發突出了。
為了解決這個問題,有些瀏覽器在請求時,用了一個非標准的Connection
字段。
Connection: keep-alive
這個字段要求服務器不要關閉TCP連接,以便其他請求復用。服務器同樣回應這個字段。
Connection: keep-alive
一個可以復用的TCP連接就建立了,直到客戶端或服務器主動關閉連接。但是,這不是標准字段,不同實現的行為可能不一致,因此不是根本的解決辦法。
Content-Type 字段
關於字符的編碼,1.0版規定,頭信息必須是 ASCII 碼,后面的數據可以是任何格式。因此,服務器回應的時候,必須告訴客戶端,數據是什么格式,這就是Content-Type
字段的作用。
下面是一些常見的Content-Type
字段的值。
- text/plain
- text/html
- text/css
- image/jpeg
- image/png
- image/svg+xml
- audio/mp4
- video/mp4
- application/javascript
- application/pdf
- application/zip
- application/atom+xml
這些數據類型總稱為MIME type
,每個值包括一級類型和二級類型,之間用斜杠分隔。
除了預定義的類型,廠商也可以自定義類型。
application/vnd.debian.binary- package
上面的類型表明,發送的是Debian系統的二進制數據包。
MIME type
還可以在尾部使用分號,添加參數。
Content-Type: text/html; charset=utf-8
上面的類型表明,發送的是網頁,而且編碼是UTF-8。
客戶端請求的時候,可以使用Accept
字段聲明自己可以接受哪些數據格式。
Accept: */*
上面代碼中,客戶端聲明自己可以接受任何格式的數據。
MIME type
不僅用在HTTP協議,還可以用在其他地方,比如HTML網頁。
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <!-- 等同於 --> <meta charset="utf-8" />
Content-Encoding 字段
由於發送的數據可以是任何格式,因此可以把數據壓縮后再發送。Content-Encoding
字段說明數據的壓縮方法。
Content-Encoding: gzip Content-Encoding: compress Content-Encoding: deflate
客戶端在請求時,用Accept-Encoding
字段說明自己可以接受哪些壓縮方法。
Accept-Encoding: gzip, deflate
HTTP/1.1
1.1 版的最大變化,就是引入了持久連接(persistent connection),即TCP連接默認不關閉,可以被多個請求復用,不用聲明Connection: keep-alive
。解決了1.0版本的keepalive問題,1.1版本加入了持久連接,一個TCP連接可以允許多個HTTP請求;
客戶端和服務器發現對方一段時間沒有活動,就可以主動關閉連接。不過,規范的做法是,客戶端在最后一個請求時,發送Connection: close
,明確要求服務器關閉TCP連接。
Connection: close
目前,對於同一個域名,大多數瀏覽器允許同時建立6個持久連接。降低了延遲同時提高了帶寬的利用率。
加入了管道機制,在同一個TCP連接里,允許多個請求同時發送,增加了並發性,進一步改善了HTTP協議的效率;舉例來說,客戶端需要請求兩個資源。以前的做法是,在同一個TCP連接里面,先發送A請求,然后等待服務器做出回應,收到后再發出B請求。管道機制則是允許瀏覽器同時發出A請求和B請求,但是服務器還是按照順序,先回應A請求,完成后再回應B請求。
Content-Length 字段
一個TCP連接現在可以傳送多個回應,勢必就要有一種機制,區分數據包是屬於哪一個回應的。這就是Content-length
字段的作用,聲明本次回應的數據長度。
Content-Length: 3495
上面代碼告訴瀏覽器,本次回應的長度是3495個字節,后面的字節就屬於下一個回應了。
在1.0版中,Content-Length
字段不是必需的,因為瀏覽器發現服務器關閉了TCP連接,就表明收到的數據包已經全了。
分塊傳輸編碼
使用Content-Length
字段的前提條件是,服務器發送回應之前,必須知道回應的數據長度。
對於一些很耗時的動態操作來說,這意味着,服務器要等到所有操作完成,才能發送數據,顯然這樣的效率不高。更好的處理方法是,產生一塊數據,就發送一塊,采用"流模式"(stream)取代"緩存模式"(buffer)。
因此,1.1版規定可以不使用Content-Length
字段,而使用"分塊傳輸編碼"(chunked transfer encoding)。只要請求或回應的頭信息有Transfer-Encoding
字段,就表明回應將由數量未定的數據塊組成。
Transfer-Encoding: chunked
每個非空的數據塊之前,會有一個16進制的數值,表示這個塊的長度。最后是一個大小為0的塊,就表示本次回應的數據發送完了。下面是一個例子。
HTTP/1.1 200 OK Content-Type: text/plain Transfer-Encoding: chunked 25 This is the data in the first chunk 1C and this is the second one 3 con 8 sequence 0
新增了請求方式PUT、PATCH、OPTIONS、DELETE等。
另外,客戶端請求的頭信息新增了Host
字段,用來指定服務器的域名。在HTTP1.0中認為每台服務器都綁定一個唯一的IP地址,因此,請求消息中的URL並沒有傳遞主機名(hostname)。但隨着虛擬主機技術的發展,在一台物理服務器上可以存在多個虛擬主機(Multi-homed Web Servers),並且它們共享一個IP地址。
Host: www.example.com
有了Host
字段,就可以將請求發往同一台服務器上的不同網站,為虛擬主機的興起打下了基礎。(實現了在一台WEB服務器上可以在同一個IP地址和端口號上使用不同的主機名來創建多個虛擬WEB站點。也即是說,web server上的多個虛擬站點可以共享同一個ip和端口。)且請求消息中如果沒有Host頭域會報告一個錯誤(400 Bad Request)。
雖然1.1版允許復用TCP連接,但是同一個TCP連接里面,所有的數據通信是按次序進行的。服務端是按隊列順序處理請求的,服務器只有處理完一個回應,才會進行下一個回應。假如前面的請求處理時間很長,后面就會有許多請求排隊等着,這樣就造成了“隊頭阻塞”的問題;同時HTTP是無狀態的連接,因此每次請求都需要添加重復的字段,降低了帶寬的利用率。
多路復用帶來一個新的問題是,在連接共享的基礎之上有可能會導致關鍵請求被阻塞。SPDY允許給每個request設置優先級,這樣重要的請求就會優先得到響應。比如瀏覽器加載首頁,首頁的html內容應該優先展示,之后才是各種靜態資源文件,腳本文件等加載,這樣可以保證用戶能第一時間看到網頁內容。
為了避免這個問題,只有兩種方法:一是減少請求數,二是同時多開持久連接。這導致了很多的網頁優化技巧,比如合並腳本和樣式表、將圖片嵌入CSS代碼、域名分片(domain sharding)等等。如果HTTP協議設計得更好一些,這些額外的工作是可以避免的。
100(Continue) Status(節約帶寬)
HTTP/1.1加入了一個新的狀態碼100(Continue)。客戶端事先發送一個只帶頭域的請求,如果服務器因為權限拒絕了請求,就回送響應碼401(Unauthorized);如果服務器接收此請求就回送響應碼100,客戶端就可以繼續發送帶實體的完整請求了。100 (Continue) 狀態代碼的使用,允許客戶端在發request消息body之前先用request header試探一下server,看server要不要接收request body,再決定要不要發request body。
HTTP/1.1在1.0的基礎上加入了一些cache的新特性,當緩存對象的Age超過Expire時變為stale對象,cache不需要直接拋棄stale對象,而是與源服務器進行重新激活(revalidation)。
HTTP 1.1支持只發送header信息(不帶任何body信息),如果服務器認為客戶端有權限請求服務器,則返回100,否則返回401。客戶端如果接受到100,才開始把請求body發送到服務器。這樣當服務器返回401的時候,客戶端就可以不用發送請求body了,節約了帶寬。
HTTP1.1還有身份認證機制,許多web站點要求用戶提供一個用戶名—口令對才能訪問存放在其服務器中的文檔,這種要求稱為身份認證(authentication)。HTTP提供特殊的狀態碼和頭部來幫助Web站點執行身份認證。
HTTP支持傳送內容的一部分。這樣當客戶端已經有一部分的資源后,只需要跟服務器請求另外的部分資源即可。這是支持文件斷點續傳的基礎。
HTTP/1.1支持文件斷點續傳,RANGE:bytes,HTTP/1.0每次傳送文件都是從文件頭開始,即0字節處開始。RANGE:bytes=XXXX表示要求服務器從文件XXXX字節處開始傳送,斷點續傳。即返回碼是206(Partial Content)
在HTTP1.1中新增了24個錯誤狀態響應碼,如409(Conflict)表示請求的資源與資源的當前狀態發生沖突;410(Gone)表示服務器上的某個資源被永久性的刪除。
HTTP/2.0
為了解決1.1版本利用率不高的問題,提出了HTTP/2.0版本。增加雙工模式,即不僅客戶端能夠同時發送多個請求,服務端也能同時處理多個請求,解決了隊頭堵塞的問題(HTTP2.0使用了多路復用的技術,做到同一個連接並發處理多個請求,而且並發請求的數量比HTTP1.1大了好幾個數量級);HTTP請求和響應中,狀態行和請求/響應頭都是些信息字段,並沒有真正的數據,因此在2.0版本中將所有的信息字段建立一張表,為表中的每個字段建立索引,客戶端和服務端共同使用這個表,他們之間就以索引號來表示信息字段,這樣就避免了1.0舊版本的重復繁瑣的字段,並以壓縮的方式傳輸,提高利用率。
另外也增加服務器推送的功能,即不經請求服務端主動向客戶端發送數據。
當前主流的協議版本還是HTTP/1.1版本。
二進制協議
HTTP/1.1 版的頭信息肯定是文本(ASCII編碼),數據體可以是文本,也可以是二進制。HTTP/2 則是一個徹底的二進制協議,頭信息和數據體都是二進制,並且統稱為"幀"(frame):頭信息幀和數據幀。
二進制協議的一個好處是,可以定義額外的幀。HTTP/2 定義了近十種幀,為將來的高級應用打好了基礎。如果使用文本實現這種功能,解析數據將會變得非常麻煩,二進制解析則方便得多。
多工
HTTP/2 復用TCP連接,在一個連接里,客戶端和瀏覽器都可以同時發送多個請求或回應,而且不用按照順序一一對應,這樣就避免了"隊頭堵塞"。
舉例來說,在一個TCP連接里面,服務器同時收到了A請求和B請求,於是先回應A請求,結果發現處理過程非常耗時,於是就發送A請求已經處理好的部分, 接着回應B請求,完成后,再發送A請求剩下的部分。
這樣雙向的、實時的通信,就叫做多工(Multiplexing)。
數據流
因為 HTTP/2 的數據包是不按順序發送的,同一個連接里面連續的數據包,可能屬於不同的回應。因此,必須要對數據包做標記,指出它屬於哪個回應。
HTTP/2 將每個請求或回應的所有數據包,稱為一個數據流(stream)。每個數據流都有一個獨一無二的編號。數據包發送的時候,都必須標記數據流ID,用來區分它屬於哪個數據流。另外還規定,客戶端發出的數據流,ID一律為奇數,服務器發出的,ID為偶數。
數據流發送到一半的時候,客戶端和服務器都可以發送信號(RST_STREAM
幀),取消這個數據流。1.1版取消數據流的唯一方法,就是關閉TCP連接。這就是說,HTTP/2 可以取消某一次請求,同時保證TCP連接還打開着,可以被其他請求使用。
客戶端還可以指定數據流的優先級。優先級越高,服務器就會越早回應。
頭信息壓縮
HTTP 協議不帶有狀態,每次請求都必須附上所有信息。所以,請求的很多字段都是重復的,比如Cookie
和User Agent
,一模一樣的內容,每次請求都必須附帶,這會浪費很多帶寬,也影響速度。
HTTP/2 對這一點做了優化,引入了頭信息壓縮機制(header compression)。一方面,頭信息使用gzip
或compress
壓縮后再發送;另一方面,客戶端和服務器同時維護一張頭信息表,所有字段都會存入這個表,生成一個索引號,以后就不發送同樣字段了,只發送索引號,這樣就提高速度了。
服務器推送
HTTP/2 允許服務器未經請求,主動向客戶端發送資源,這叫做服務器推送(server push)。
意思是說,當我們對支持HTTP2.0的web server請求數據的時候,服務器會順便把一些客戶端需要的資源一起推送到客戶端,免得客戶端再次創建連接發送請求到服務器端獲取。這種方式非常合適加載靜態資源。
服務器端推送的這些資源其實存在客戶端的某處地方,客戶端直接從本地加載這些資源就可以了,不用走網絡,速度自然是快很多的。
常見場景是客戶端請求一個網頁,這個網頁里面包含很多靜態資源。正常情況下,客戶端必須收到網頁后,解析HTML源碼,發現有靜態資源,再發出靜態資源請求。其實,服務器可以預期到客戶端請求網頁后,很可能會再請求靜態資源,所以就主動把這些靜態資源隨着網頁一起發給客戶端了。
服務端推送能把客戶端所需要的資源伴隨着index.html一起發送到客戶端,省去了客戶端重復請求的步驟。正因為沒有發起請求,建立連接等操作,所以靜態資源通過服務端推送的方式可以極大地提升速度。
普通的客戶端請求過程:
服務端推送的過程:
HTTP 性能優化的關鍵並不在於高帶寬,而是低延遲。TCP 連接會隨着時間進行自我「調諧」,起初會限制連接的最大速度,如果數據成功傳輸,會隨着時間的推移提高傳輸的速度。這種調諧則被稱為 TCP 慢啟動(擁塞控制)。由於這種原因,讓原本就具有突發性和短時性的 HTTP 連接變的十分低效。
HTTP/2 通過讓所有數據流共用同一個連接,可以更有效地使用 TCP 連接,讓高帶寬也能真正的服務於 HTTP 的性能提升。
二、HTTP響應模型
服務器收到HTTP請求之后,會有多種方法響應這個請求,下面是HTTP響應的四種模型:
單進程I/O模型
服務端開啟一個進程,一個進程僅能處理一個請求,並且對請求順序處理;
多進程I/O模型
服務端並行開啟多個進程,同樣的一個進程只能處理一個請求,這樣服務端就可以同時處理多個請求;
復用I/O模型
服務端開啟一個進程,但是呢,同時開啟多個線程,一個線程響應一個請求,同樣可以達到同時處理多個請求,線程間並發執行;
復用多線程I/O模型
服務端並行開啟多個進程,同時每個進程開啟多個線程,這樣服務端可以同時處理進程數M*每個進程的線程數N個請求。