HTTP 的特性
- HTTP 協(xié)議構建于 TCP/IP 協(xié)議之上裆馒,是一個應用層協(xié)議姊氓,默認端口號是 80
- HTTP 是無連接無狀態(tài)的
HTTP 報文
請求報文
HTTP 協(xié)議是以 ASCII 碼傳輸,建立在 TCP/IP 協(xié)議之上的應用層規(guī)范喷好。規(guī)范把 HTTP 請求分為三個部分:狀態(tài)行他膳、請求頭、消息主體绒窑。類似于下面這樣:
<method> <request-URL> <version>
<headers>
<entity-body>
HTTP 定義了與服務器交互的不同方法棕孙,最基本的方法有4種,分別是GET些膨,POST蟀俊,PUT,DELETE订雾。URL全稱是資源描述符肢预,我們可以這樣認為:一個URL地址,它用于描述一個網(wǎng)絡上的資源洼哎,而 HTTP 中的GET烫映,POST,PUT噩峦,DELETE就對應著對這個資源的查锭沟,增,改识补,刪4個操作族淮。
GET 用于信息獲取,而且應該是安全的 和 冪等的凭涂。
所謂安全的意味著該操作用于獲取信息而非修改信息祝辣。換句話說,GET 請求一般不應產(chǎn)生副作用切油。就是說蝙斜,它僅僅是獲取資源信息,就像數(shù)據(jù)庫查詢一樣澎胡,不會修改孕荠,增加數(shù)據(jù)绢片,不會影響資源的狀態(tài)。
冪等的意味著對同一 URL 的多個請求應該返回同樣的結果岛琼。
GET 請求報文示例:
GET /books/?sex=man&name=Professional HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Connection: Keep-Alive
POST 表示可能修改變服務器上的資源的請求底循。
POST / HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 40
Connection: Keep-Alive
sex=man&name=Professional
注意:GET 可提交的數(shù)據(jù)量受到URL長度的限制,HTTP 協(xié)議規(guī)范沒有對 URL 長度進行限制槐瑞。這個限制是特定的瀏覽器及服務器對它的限制理論上講熙涤,POST 是沒有大小限制的,HTTP 協(xié)議規(guī)范也沒有進行大小限制困檩,出于安全考慮祠挫,服務器軟件在實現(xiàn)時會做一定限制參考上面的報文示例,可以發(fā)現(xiàn) GET 和 POST 數(shù)據(jù)內(nèi)容是一模一樣的悼沿,只是位置不同等舔,一個在 URL 里,一個在 HTTP 包的包體里糟趾。
POST 提交數(shù)據(jù)的方式
HTTP 協(xié)議中規(guī)定 POST 提交的數(shù)據(jù)必須在 body 部分中慌植,但是協(xié)議中沒有規(guī)定數(shù)據(jù)使用哪種編碼方式或者數(shù)據(jù)格式。實際上义郑,開發(fā)者完全可以自己決定消息主體的格式蝶柿,只要最后發(fā)送的 HTTP 請求滿足上面的格式就可以。
但是非驮,數(shù)據(jù)發(fā)送出去特石,還要服務端解析成功才有意義歧沪。一般服務端語言如 PHP、Python 等蹂空,以及它們的 framework裂问,都內(nèi)置了自動解析常見數(shù)據(jù)格式的功能溶握。服務端通常是根據(jù)請求頭(headers)中的 Content-Type 字段來獲知請求中的消息主體是用何種方式編碼脊凰,再對主體進行解析燕垃。所以說到 POST 提交數(shù)據(jù)方案,包含了 Content-Type 和消息主體編碼方式兩部分栋盹。下面就正式開始介紹它們:
application/x-www-form-urlencoded
這是最常見的 POST 數(shù)據(jù)提交方式施逾。瀏覽器的原生 <form> 表單敷矫,如果不設置 enctype 屬性例获,那么最終就會以 application/x-www-form-urlencoded 方式提交數(shù)據(jù)。上個小節(jié)當中的例子便是使用了這種提交方式曹仗≌ヌ溃可以看到 body 當中的內(nèi)容和 GET 請求是完全相同的。multipart/form-data
這又是一個常見的 POST 數(shù)據(jù)提交的方式怎茫。我們使用表單上傳文件時收壕,必須讓 <form> 表單的 enctype 等于 multipart/form-data妓灌。直接來看一個請求示例:
POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="text"
title
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png
PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--
這個例子稍微復雜點。首先生成了一個 boundary 用于分割不同的字段蜜宪,為了避免與正文內(nèi)容重復虫埂,boundary 很長很復雜。然后 Content-Type
里指明了數(shù)據(jù)是以 multipart/form-data
來編碼圃验,本次請求的 boundary 是什么內(nèi)容掉伏。消息主體里按照字段個數(shù)又分為多個結構類似的部分,每部分都是以 --boundary 開始澳窑,緊接著是內(nèi)容描述信息斧散,然后是回車,最后是字段具體內(nèi)容(文本或二進制)摊聋。如果傳輸?shù)氖俏募瑁€要包含文件名和文件類型信息。消息主體最后以 --boundary-- 標示結束麻裁。關于 multipart/form-data
的詳細定義箍镜,請前往 RFC1867 查看(或者相對友好一點的 MDN 文檔)。
這種方式一般用來上傳文件煎源,各大服務端語言對它也有著良好的支持鹿寨。
上面提到的這兩種 POST 數(shù)據(jù)的方式,都是瀏覽器原生支持的薪夕,而且現(xiàn)階段標準中原生 <form>
表單也只支持這兩種方式(通過 <form>
元素的 enctype 屬性指定脚草,默認為 application/x-www-form-urlencoded
。其實 enctype 還支持 text/plain原献,不過用得非常少)馏慨。
隨著越來越多的 Web 站點,尤其是 WebApp姑隅,全部使用 Ajax 進行數(shù)據(jù)交互之后写隶,我們完全可以定義新的數(shù)據(jù)提交方式,例如 application/json
讲仰,text/xml
慕趴,乃至 application/x-protobuf
這種二進制格式,只要服務器可以根據(jù) Content-Type
和 Content-Encoding
正確地解析出請求鄙陡,都是沒有問題的冕房。
響應報文
HTTP 響應與 HTTP 請求相似,HTTP響應也由3個部分構成趁矾,分別是:
- 狀態(tài)行
- 響應頭(Response Header)
- 響應正文
狀態(tài)行由協(xié)議版本耙册、數(shù)字形式的狀態(tài)代碼、及相應的狀態(tài)描述毫捣,各元素之間以空格分隔详拙。
常見的狀態(tài)碼有如下幾種:
- 200 OK 客戶端請求成功
- 301 Moved Permanently 請求永久重定向
- 302 Moved Temporarily 請求臨時重定向
- 304 Not Modified 文件未修改帝际,可以直接使用緩存的文件。
- 400 Bad Request 由于客戶端請求有語法錯誤饶辙,不能被服務器所理解蹲诀。
- 401 Unauthorized 請求未經(jīng)授權。這個狀態(tài)代碼必須和WWW-Authenticate報頭域一起使用
- 403 Forbidden 服務器收到請求弃揽,但是拒絕提供服務侧甫。服務器通常會在響應正文中給出不提供服務的原因
- 404 Not Found 請求的資源不存在,例如蹋宦,輸入了錯誤的URL
- 500 Internal Server Error 服務器發(fā)生不可預期的錯誤披粟,導致無法完成客戶端的請求。
- 503 Service Unavailable 服務器當前不能夠處理客戶端的請求冷冗,在一段時間之后守屉,服務器可能會恢復正常。
下面是一個HTTP響應的例子:
HTTP/1.1 200 OK
Server:Apache Tomcat/5.0.12
Date:Mon,6Oct2003 13:23:42 GMT
Content-Length:112
<html>...
持久連接
我們知道 HTTP 協(xié)議采用“請求-應答”模式蒿辙,當使用普通模式拇泛,即非 Keep-Alive 模式時,每個請求/應答客戶和服務器都要新建一個連接思灌,完成之后立即斷開連接(HTTP 協(xié)議為無連接的協(xié)議)俺叭;當使用 Keep-Alive 模式(又稱持久連接、連接重用)時泰偿,Keep-Alive 功能使客戶端到服務器端的連接持續(xù)有效熄守,當出現(xiàn)對服務器的后繼請求時,Keep-Alive 功能避免了建立或者重新建立連接耗跛。
在 HTTP 1.0 版本中裕照,并沒有官方的標準來規(guī)定 Keep-Alive 如何工作,因此實際上它是被附加到 HTTP 1.0協(xié)議上调塌,如果客戶端瀏覽器支持 Keep-Alive 晋南,那么就在HTTP請求頭中添加一個字段 Connection: Keep-Alive,當服務器收到附帶有 Connection: Keep-Alive 的請求時羔砾,它也會在響應頭中添加一個同樣的字段來使用 Keep-Alive 负间。這樣一來,客戶端和服務器之間的HTTP連接就會被保持姜凄,不會斷開(超過 Keep-Alive 規(guī)定的時間政溃,意外斷電等情況除外),當客戶端發(fā)送另外一個請求時檀葛,就使用這條已經(jīng)建立的連接玩祟。
在 HTTP 1.1 版本中,默認情況下所有連接都被保持屿聋,如果加入 "Connection: close" 才關閉空扎。目前大部分瀏覽器都使用 HTTP 1.1 協(xié)議,也就是說默認都會發(fā)起 Keep-Alive 的連接請求了润讥,所以是否能完成一個完整的 Keep-Alive 連接就看服務器設置情況转锈。
由于 HTTP 1.0 沒有官方的 Keep-Alive 規(guī)范,并且也已經(jīng)基本被淘汰楚殿,以下討論均是針對 HTTP 1.1 標準中的 Keep-Alive 展開的撮慨。
注意:
HTTP Keep-Alive 簡單說就是保持當前的TCP連接,避免了重新建立連接脆粥。
HTTP 長連接不可能一直保持砌溺,例如
Keep-Alive: timeout=5, max=100
,表示這個TCP通道可以保持5秒变隔,max=100规伐,表示這個長連接最多接收100次請求就斷開。HTTP 是一個無狀態(tài)協(xié)議匣缘,這意味著每個請求都是獨立的猖闪,Keep-Alive 沒能改變這個結果。另外肌厨,Keep-Alive也不能保證客戶端和服務器之間的連接一定是活躍的培慌,在 HTTP1.1 版本中也如此。唯一能保證的就是當連接被關閉時你能得到一個通知柑爸,所以不應該讓程序依賴于 Keep-Alive 的保持連接特性吵护,否則會有意想不到的后果。
使用長連接之后表鳍,客戶端何址、服務端怎么知道本次傳輸結束呢?兩部分:1. 判斷傳輸數(shù)據(jù)是否達到了Content-Length 指示的大薪琛用爪;2. 動態(tài)生成的文件沒有 Content-Length ,它是分塊傳輸(chunked)胁镐,這時候就要根據(jù) chunked 編碼來判斷,chunked 編碼的數(shù)據(jù)在最后有一個空 chunked 塊盯漂,表明本次傳輸數(shù)據(jù)結束,詳見這里帖渠。什么是 chunked 分塊傳輸呢?下面我們就來介紹一下竭宰。
Transfer-Encoding
Transfer-Encoding 是一個用來標示 HTTP 報文傳輸格式的頭部值空郊。盡管這個取值理論上可以有很多,但是當前的 HTTP 規(guī)范里實際上只定義了一種傳輸取值——chunked锁摔。
如果一個HTTP消息(請求消息或應答消息)的Transfer-Encoding消息頭的值為chunked,那么哼审,消息體由數(shù)量未定的塊組成谐腰,并以最后一個大小為0的塊為結束。
每一個非空的塊都以該塊包含數(shù)據(jù)的字節(jié)數(shù)(字節(jié)數(shù)以十六進制表示)開始涩盾,跟隨一個CRLF (回車及換行),然后是數(shù)據(jù)本身砸西,最后塊CRLF結束终畅。在一些實現(xiàn)中,塊大小和CRLF之間填充有白空格(0x20)离福。
最后一塊是單行,由塊大械(0)絮识,一些可選的填充白空格,以及CRLF次舌。最后一塊不再包含任何數(shù)據(jù)彼念,但是可以發(fā)送可選的尾部,包括消息頭字段逐沙。消息最后以CRLF結尾。
一個示例響應如下:
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
25
This is the data in the first chunk
1A
and this is the second one
0
注意:
- chunked 和 multipart 兩個名詞在意義上有類似的地方棚赔,不過在 HTTP 協(xié)議當中這兩個概念則不是一個類別的。multipart 是一種 Content-Type丧肴,標示 HTTP 報文內(nèi)容的類型,而 chunked 是一種傳輸格式捆毫,標示報頭將以何種方式進行傳輸冲甘。
- chunked 傳輸不能事先知道內(nèi)容的長度,只能靠最后的空 chunk 塊來判斷濒憋,因此對于下載請求來說陶夜,是沒有辦法實現(xiàn)進度的。在瀏覽器和下載工具中黔夭,偶爾我們也會看到有些文件是看不到下載進度的羽嫡,即采用 chunked 方式進行下載。
- chunked 的優(yōu)勢在于婚惫,服務器端可以邊生成內(nèi)容邊發(fā)送魂爪,無需事先生成全部的內(nèi)容。HTTP/2 不支持 Transfer-Encoding: chunked滓侍,因為 HTTP/2 有自己的 streaming 傳輸方式(Source:MDN - Transfer-Encoding)撩笆。
HTTP Pipelining(HTTP 管線化)
默認情況下 HTTP 協(xié)議中每個傳輸層連接只能承載一個 HTTP 請求和響應,瀏覽器會在收到上一個請求的響應之后浇衬,再發(fā)送下一個請求耘擂。在使用持久連接的情況下,某個連接上消息的傳遞類似于請求1 -> 響應1 -> 請求2 -> 響應2 -> 請求3 -> 響應3
。
HTTP Pipelining(管線化)是將多個 HTTP 請求整批提交的技術篙悯,在傳送過程中不需等待服務端的回應铃绒。使用 HTTP Pipelining 技術之后,某個連接上的消息變成了類似這樣請求1 -> 請求2 -> 請求3 -> 響應1 -> 響應2 -> 響應3
矮燎。
注意下面幾點:
- 管線化機制通過持久連接(persistent connection)完成赔癌,僅 HTTP/1.1 支持此技術(HTTP/1.0不支持)
- 只有 GET 和 HEAD 請求可以進行管線化,而 POST 則有所限制
- 初次創(chuàng)建連接時不應啟動管線機制峡谊,因為對方(服務器)不一定支持 HTTP/1.1 版本的協(xié)議
- 管線化不會影響響應到來的順序刊苍,如上面的例子所示,響應返回的順序并未改變
- HTTP /1.1 要求服務器端支持管線化啥纸,但并不要求服務器端也對響應進行管線化處理埠忘,只是要求對于管線化的請求不失敗即可
- 由于上面提到的服務器端問題,開啟管線化很可能并不會帶來大幅度的性能提升名船,而且很多服務器端和代理程序對管線化的支持并不好旨怠,因此現(xiàn)代瀏覽器如 Chrome 和 Firefox 默認并未開啟管線化支持
更多關于 HTTP Pipelining 的知識可以參考這里。
會話跟蹤
什么是會話迷扇?
客戶端打開與服務器的連接發(fā)出請求到服務器響應客戶端請求的全過程稱之為會話爽哎。
什么是會話跟蹤?
會話跟蹤指的是對同一個用戶對服務器的連續(xù)的請求和接受響應的監(jiān)視课锌。
為什么需要會話跟蹤?
瀏覽器與服務器之間的通信是通過HTTP協(xié)議進行通信的雏胃,而HTTP協(xié)議是”無狀態(tài)”的協(xié)議,它不能保存客戶的信息方仿,即一次響應完成之后連接就斷開了统翩,下一次的請求需要重新連接,這樣就需要判斷是否是同一個用戶鳍征,所以才有會話跟蹤技術來實現(xiàn)這種要求面徽。
會話跟蹤常用的方法:
(1)URL 重寫
URL(統(tǒng)一資源定位符)是Web上特定頁面的地址匣掸,URL重寫的技術就是在URL結尾添加一個附加數(shù)據(jù)以標識該會話,把會話ID通過URL的信息傳遞過去,以便在服務器端進行識別不同的用戶霎匈。
(2)隱藏表單域
將會話ID添加到HTML表單元素中提交到服務器送爸,此表單元素并不在客戶端顯示
(3)Cookie
Cookie 是Web 服務器發(fā)送給客戶端的一小段信息,客戶端請求時可以讀取該信息發(fā)送到服務器端墨吓,進而進行用戶的識別纹磺。對于客戶端的每次請求,服務器都會將 Cookie 發(fā)送到客戶端,在客戶端可以進行保存,以便下次使用秘症。
客戶端可以采用兩種方式來保存這個 Cookie 對象式矫,一種方式是保存在客戶端內(nèi)存中,稱為臨時 Cookie聪廉,瀏覽器關閉后這個 Cookie 對象將消失。另外一種方式是保存在客戶機的磁盤上锄列,稱為永久 Cookie邻邮。以后客戶端只要訪問該網(wǎng)站,就會將這個 Cookie 再次發(fā)送到服務器上筒严,前提是這個 Cookie 在有效期內(nèi),這樣就實現(xiàn)了對客戶的跟蹤摹恨。
Cookie 是可以被客戶端禁用的娶视。
(4)Session:
每一個用戶都有一個不同的 session,各個用戶之間是不能共享的寝凌,是每個用戶所獨享的孝赫,在 session 中可以存放信息。
在服務器端會創(chuàng)建一個 session 對象伐债,產(chǎn)生一個 sessionID 來標識這個 session 對象致开,然后將這個 sessionID 放入到 Cookie 中發(fā)送到客戶端,下一次訪問時祖今,sessionID 會發(fā)送到服務器拣技,在服務器端進行識別不同的用戶。
Session 的實現(xiàn)依賴于 Cookie徐绑,如果 Cookie 被禁用莫辨,那么 session 也將失效毅访。