瀏覽器如何確定http的請求首部欄位?

最近在學習http協議,但是有個問題一直不太清楚,就是http請求的首部欄位有這麼多,那麼在發送一個請求的時候,怎麼去確定這個請求中的首部欄位應該需要包含哪些首部欄位?


我維護一個Http客戶端框架一年多了,我提供一點點拋磚引玉的線索吧。

我在最初開發這個Http客戶端框架的時候為了儘可能做到和瀏覽器相同的功能,也學習了很多Http1.1中的知識,所以先拋出兩個連接:

1. Http協議1.1,RFC2616是最早的一個完整定稿,現在很多成熟的網路框架和瀏覽器都是參考這個文檔的標準來做的:http://www.ietf.org/rfc/rfc2616.txt ,當然文檔在修訂升級,所以還有其它文檔也可以參考,RFC的文檔List在這裡:Index of /rfc 。

2. Http響應碼說明,我一般參考來自w3c的:HTTP 狀態消息

客戶端的Http Header

比如這個URL:

www.yanzhenjie.com?name=yzjpwd=123

如果是GET請求,URL中查詢字元串(名稱/值對)是在 GET 請求的URL中發送的。什麼也不需要說明的時候,最基本的請求頭如下:

GET HTTP/1.1

Host: http://www.yanzhenjie.com

如果URL中還帶有路徑,比如:

www.yanzhenjie.com/test/xxoo.action?name=yzjpwd=123

它的最基本的請求頭如下:

GET //xxoo.action HTTP/1.1

Host: http://www.yanzhenjie.com

如果是POST請求,除了第一行的GET變為POST,URL中查詢字元串還是通過URL發送,其它參數是通過body發送的,但是請求頭是沒變的,其它請求方法也一樣。

其它請求方法有特例,下文中會說明。

不同請求頭的含義,請求頭有很多,我這裡只說明較為重要的幾個,其它我會列出,但不做說明。請求頭也可以由客戶端開發和伺服器開發協商添加自定義頭,不過因為我們伺服器(Tomcat、Apache)和瀏覽器是遵守標準協議中的欄位的,所以我只說標準協議中規定的。

Accept表示客戶端接受什麼數據,但是只是表示期望,伺服器支持你的期望才會返回對應格式數據,比如希望接受json:application/json,接受xml:application/xml,接受圖片:image/*,接受文本:text/*,接受html:text/html,接受全部:*/*,當然表示方法不止一種,比如圖片也可以這樣:image/png、image/jpeg、image/webp等。而且還可以加上你的期望值:q=0.5,這個q的取值在0-1之間,0就沒意義了,1表示100%,如果不加默認為1,一般瀏覽器的Accept都會寫:

text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

Accept-Charset表示接受編碼的字符集,比如UTF-8、ISO-8859-1。

Accept-Encoding表示客戶端接受的數據壓縮演算法,比如:gzip、deflate,一般都會支持gzip,不過chrome內核的瀏覽器還有: sdch, br,所以瀏覽器一般這樣寫:gzip, deflate, sdch。

Accept-Language表示客戶端希望接受的語言,比如:zh-CN,zh、en-US,en等,前者表示中文-簡體,後者表示英文-美國;這也是很多網站做國際化的一個方案。

Accept-Range表示接受數據的一個或者多個子範圍,這個欄位用的較少,一般我們:Accept-Ranges: bytes。

Authorization應該見的很多,表示Http授權,取值一般是Basic+1個空格+簽名,例如:

Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

Connection表示是否需要持久連接,值一般有: close 、Keep-Alive,而Http1.1中規定了默認就是持久連接,如果希望不就持久,那就選擇close;如果希望持久,為了兼容Http1.0我們一般會加一個Keep-Alive。

Cookie是對話的session和普通cookie,我們知道伺服器會自動為一個http連接設置session,也可以添加cookie,設置的session和cookie都會通過相應頭的Set-Cookie發送給客戶端,正常情況下客戶端會把該請求域名下的所有cookie值一起發送給伺服器。

Content-Length表示請求體的長度,一般用於POST、PUT、DELETE、PATCH請求,值是int或者long型數據,比如:7788。

Content-Type表示請求提的類型(MimeType),這個欄位的取值需要特別說明

一般URL類型參數時:application/x-www-form-urlencoded

提交表單時:multipart/form-data

包體是JSON時:application/json

包體是XML數據流時:application/xml

包體是未知數據流時:application/octet-stream

...

Referer表示當前請求的上一個請求地址,這個一般第一個請求不會添加,第二個請求如果還是當前host下的地址可以添加,如果域不同就不添加,當然也可以不管這個欄位,建議添加。

Range表示客戶端接受數據開始的位元組位置,一般用於斷點續傳文件下載,格式:bytes=xx-oo。比如一個2M(2 * 1024 * 1024)的文件,我下載到1M(1 * 1024 * 1024)時停止了(也就是暫停),那麼繼續下載時在請求時就會添加Range頭:bytes=1048576-,-後面沒東西就表示下載一直到文件結尾處,一般多線程下載就是使用這個原理。

If-Modified-Since表示伺服器上一次返回的內容被伺服器修改的時間,如果伺服器沒返回,本次請求就不帶這個頭,可能不太好理解。如果伺服器支持,在返回一個html的時候,會設置一個相應頭:Last-Modified,這個相應頭的含義是伺服器最後一次修改返回內容的時間,返回的時間格式是格林尼治時間,格式如下:

EEE, dd MMM y HH:mm:ss 『GMT』

客戶端在接受到響應後應該保存這個頭(一般會轉化為毫秒,具體看這篇文章:Http格林尼治時間和毫秒的相互轉化EEE, dd MMM y HH:mm:ss #x27;GMT#x27;),在下一次請求這個介面的時候用If-Modified-Since把轉化後的毫秒作為值帶上。當然還要結合伺服器上次返回的Cache-Control(Http1.0中是Pragma)頭來做判斷。

If-None-Match表示上次請求這個介面時伺服器緩存的ETag,如果上次沒有返回則不帶這個頭。

對緩存做一個說明,伺服器希望客戶端緩存某次請求的文檔時,會返回Last-Modified和Etag兩個頭,客戶端第二次會用If-Modified-Since和If-None-Match請求時帶上這兩個頭,伺服器會做判斷,如果If-Modified-Since的時間和文檔的修改時間一致或者更小並且ETag一直,說明伺服器的文檔沒有被修改過,伺服器會返回304響應碼,告訴客戶端上次請求緩存起來的文檔還是有效的。比如一個文檔有500M大,那麼只需要幾秒鐘請求做下緩存驗證即可,不必要花好多時間再來通過流來傳輸文檔了。

關於伺服器的Cache-Control頭:大家都聽說過離線緩存吧,離線緩存就要使用到Cache-Control頭,它的取值有:no-cache、no-store、max-age=xxoo、stale-while-revalidate、其它;這裡只說明前三個:no-store的意思是不緩存;no-cache的意思是可以緩存,但是每次請求時需要向伺服器做緩存校驗(上面已經說了);max-age=xxoo,這個xxoo的單位是秒,含義是本次請求的響應體從現在開始的最大有效時間,在這個時間段內,客戶端可不向伺服器做緩存校驗,一般和伺服器返回的Date頭結合使用(伺服器Date表示伺服器響應時的時間),我們以伺服器時間為準。我是這麼做的:用本地時間加max-age並保存,在下次請求時用保存起來的時間減去新獲取的本地時間,這個時間如果大於等於0,說明緩存沒過期直接使用,如果小於0說明過期,需要向伺服器過緩存校驗(伺服器可能會返回304,同時也可能會更新Cache-Control頭,也可能返回200和新數據)。

User-Agent表示客戶端的環境信息,一般包涵瀏覽器內核、硬體信息、系統信息等等,比如Chrome瀏覽器:

Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Mobile Safari/537.36

其它頭說明:其它頭在上面提到過,就不再重複解釋了,這裡還有寫沒提到的不太常用的頭:If-Match If-Range If-Unmodified-Since Expect From Max-Forwards Pragma Proxy-Authorization Upgrade,讀者可以自行了解一下。

伺服器的Http Header:

上面其實已經說到了很多伺服器的頭了,下面撿幾個重要的說明吧。

Date表示伺服器響應時的時間,返回的格式是格林尼治時間(有的出版局叫格林威治Greenwhich)的格式:」EEE, dd MMM y HH:mm:ss 『GMT』」。

Location表示伺服器的地址已經變更,一般是響應碼在300-400之間才會有(304表示緩存重定向,不會返回這個頭),這個頭的值就是新的url、也可能是相對路徑,

比如客戶端請求:

www.yanzhenjie.com/test/index.html

伺服器返回的Location可能是

/xxoo/index.html

那麼客戶端重定向需要請求的地址是:

www.yanzhenjie.com/xxoo/index.html

Content-Encoding表示伺服器返回內容的壓縮演算法,對應客戶端端請求頭Accept-Encoding,返回值可能是:gzip等。

與Content-Encoding一樣,伺服器的Content-Language、Content-Length、Content-MD5、Content-Type等就不用過多解釋了吧。

Server表示伺服器軟體名稱,比如Apache、Tomcat、Nginx等。

Set-Cookie表示此次伺服器給客戶端設置的Cookie,格式大致如下:

name=value; expires=Tue, 25-Apr-17 04:57:47 GMT; domain=域名; path=/

其中name是cookie的名字,value是cookie的值,expires表示cookie的過期時間(格式是格林尼治時間),domain表示作用域的域名,path表示在這個域名下生效的路徑。

對應的是客戶端的請求頭:Cookie,請求的時候提交的Cookie只有name和value:

Cookie: name=value;name1=value1;name2=value2;

看到了吧,請求頭是可以帶多個Cookie的,響應的時候因為每個Set-Cookie包涵的屬性較多,所以每一個Set-Cookie都看作一個對象,所以需要Set-Cookie多個,我們在響應的時候看到的如下:

name1=value1; expires=Tue, 25-Apr-17 04:57:47 GMT; domain=域名; path=/
name2=value2; expires=Tue, 25-Apr-17 04:57:47 GMT; domain=域名; path=/
name3=value3; expires=Tue, 25-Apr-17 04:57:47 GMT; domain=域名; path=/

結束

常用的頭基本上都有介紹到,如果有解釋錯誤的地方請指出,我會儘快更正。


HTTP請求,只有第一行是有用的,其他都是選項。

選項嘛,就是你選也行,不選也行。

基本原則是,表達你的主觀訴求。即你想讓伺服器給你什麼信息。

比如獲取首頁:

GET / HTTP/1.1

就這一行就夠了。真的只要這一行就夠了。

所以HTTP請求最簡單的形式就是這樣。其他的都是可以省略的。

有些伺服器上存了不止一個網站,所以我們需要Host選項。比如:

GET / HTTP/1.1
Host: www.zhihu.com

顯然,那些伺服器上只有一個網站的伺服器也能理解這個請求。所以我們就不區分哪些伺服器上只有一個網站,哪些有多個。統一髮帶Host這種了。

有些網頁特別大,壓縮網頁可以節省傳輸時間。但是並不是所有瀏覽器都支持。對於伺服器來說,如果一個瀏覽器表名了它支持壓縮,那我就用壓縮的格式發給它數據。

那麼瀏覽器怎麼標明自己可以支持壓縮呢?用Accept-Encoding選項。

GET / HTTP/1.1
Host: www.zhihu.com
Accept-Encoding: gzip

那麼瀏覽器怎麼知道伺服器是不是支持壓縮呢?

當然是請求一下了。

如果伺服器不支持,它會在響應頭裡告訴你,然後返回給你未壓縮的網頁。

所以說,請求頭是表達自己請求的希望的。你的請求未必都會被滿足,所以只要表達自己想要什麼,就請求什麼就好。

很多人在學習請求頭的時候都問過一個問題:我怎麼知道伺服器是不是支持這個請求。

答案是:你不需要按照伺服器支持去發送請求。既然是請求,當然是發送自己希望的東西。

不要忘了HTTP還有響應頭這麼個東西。


推薦閱讀:

如何評論瀏覽器最新的 WebAssembly 位元組碼技術?
瀏覽器是不是相當於 HTML、CSS 等語言的一個解釋器?
把瀏覽器的 rendering engine 翻譯成「渲染引擎」是正確的嗎?
"去你大爺的內置瀏覽器"是幹什麼的?

TAG:網頁瀏覽器 | HTTP |