原文:HTTP Made Really Easy因為我本身網絡基礎就很差,是以看到這篇文章一方面是學習網絡知識,另一方面為了鍛煉我蹩腳的英語水準,文中如有錯誤,歡迎浏覽指正!
前言
在看這篇文章的時候,推薦使用chrome浏覽器檢視http請求過程中的相關參數。chrome浏覽器,可以通過‘alt+cmd+i’進入開發者模式。進入‘Network’一欄,在‘Name’欄内找到請求的網址。檢視Headers一欄,就可以看到‘Response Headers’和‘Request Headers’。并可以選擇‘view parsed’和‘view source’。下面以http://www.xueweihan.com 為例子示範一下。如下圖:

HTTP是一種網絡協定,它十分簡單但強大。知曉HTTP協定,使你可以寫一個:Web浏覽器,Web伺服器,爬蟲或者其他實用的工具。
這是個通俗易懂,講解HTTP的文章。講述了一些HTTP clients和servers實作上的細節。看這篇文章需要你有socket網絡程式設計的基礎,HTTP對于會socket程式設計的程式員來說十分簡單。是以請先確定你學會了socket程式設計(看來我要寫一個socket程式設計的教程了!)同時搞懂了CGI。
這篇文章前半段,是基于HTTP 1.0,下半部分是解釋HTTP 1.1的新特性。雖然沒有覆寫HTTP所有的點,但是會讓你對HTTP有一個基本的架構,之後你可以根據你的需求,再深入全面的學習。
在文章的開始之前,先來看看下面的兩段文字:
寫一個網絡應用,需要比寫一個單機程式更加小心,考慮的東西要更多!當然你必須要符合标準(也就是協定),否則沒人可以能了解你。更加重要的是:你寫的垃圾程式,運作在你的機器上,隻會浪費你的機器的資源(CPU,帶寬,記憶體)。如果一個垃圾的網絡程式,你将會浪費其他人的資源。如果是特别垃圾的程式,那麼将會浪費成千上萬人的資源。這些情況使得建立更好的,安全的網絡協定,對每個人都是有益的!
不要盲目的去寫爬蟲,除非你十厘清楚你自己在做什麼。爬蟲是很實用,但是垃圾的爬蟲,不符合規則的爬蟲,使得網絡環境越來越錯綜複雜。如果你想要寫任何一個‘爬蟲機器人’,請遵循robots.txt中的内容。
HTTP是超文本傳輸協定,是一種用于在網際網路,傳輸檔案,無論是HTML檔案,還是圖檔,請求等資料的網絡協定。通常HTTP協定是建立在TCP/IP socket通信基礎之上的。
HTTP協定是用戶端(client)和伺服器(servers)通信的協定。浏覽器就是一個用戶端,因為它發送請求給HTTP伺服器(也就是web伺服器),web伺服器傳回響應給用戶端。符合HTTP協定的伺服器,預設監聽80端口,當然也可以重新指定任何一個端口。
HTTP協定是用來傳播資源,而不僅僅隻是檔案。資源就是一個URL連結所對應的一些資訊。我們普遍見到的資源就是檔案,同時資源也可能是:通過不同程式設計語言寫的CGI腳本,動态生成,并輸出。傳回請求結果的檔案。
學習HTTP,有助于了解資源類似于檔案的概念。實際場景中,HTTP資源不是靜态檔案,就是伺服器端腳本動态生成的結果。
就像大多數的網絡協定,HTTP也是C/S模式:用戶端向伺服器發送請求連接配接和請求的資訊内容,伺服器傳回響應資訊。通常包含請求的資源。伺服器發送完響應後,關閉連接配接。(HTTP是一個無狀态的連接配接)
請求和響應的格式長得差不多,它們都是由:
一條初始行
零或多條頭資訊
一個空行
一個可選的消息體
組成的,格式如下,:
<code>initial line</code>和<code>headers</code>必須由<code>回車</code>結尾。
請求的第一行和響應的第一行不一樣!請求的第一行有三個部分:方法名,請求資源的路徑(也就是/分隔的路徑),使用的HTTP協定版本。通路我的部落格首頁時,請求頭如下:
注意:
GET方法是HTTP中最常用的方法,他的意思是:‘我要得到這個資源’。另外一個常用的方法是:POST後面會在做詳細的說明。方法名全部大寫。
域名後面的部分就是路徑,預設是‘/’。
HTTP版本形如:‘HTTP/x.x’,全部大寫。
響應的初始行,稱作‘狀态行’。也是由三個部分組成的:HTTP協定版本,狀态碼,狀态碼描述。同樣以我部落格為例子,狀态行如下:
注意:
HTTP版本的内容,形式跟上面一樣。
狀态碼是三位的整數,第一位通常分為如下幾類:
1xx 這一類型的狀态碼,代表請求已被接受,需要繼續處理。(消息)
2xx 這一類型的狀态碼,代表請求已成功被伺服器接收、了解、并接受。(成功)
3xx 這類狀态碼代表需要用戶端采取進一步的操作才能完成請求。(重定向)
4xx 這類的狀态碼代表了用戶端看起來可能發生了錯誤,妨礙了伺服器的處理。(用戶端錯誤)
5xx 這類狀态碼代表了伺服器在處理請求的過程中有錯誤或者異常狀态發生。(伺服器錯誤)
常見的狀态碼:
200 OK:請求成功,接收到資源。
404 Not Found:請求失敗,未找到資源。
301 Moved Permanently:永久性轉移。
302 Moved Temporarily:暫時性轉移。
303 See Other:請求的資源已經移到了另外一個URL上了,用戶端會自動跳轉。這個通常是CGI腳本使用<code>redirect</code>,使得用戶端,重定向到另外一個URL。
500 Server Error:一個未知的伺服器錯誤。
請求頭提供了請求或響應的資訊,或者是關于發送的消息體(message body)的資訊。
請求頭的格式為:每條頭資訊占一行例如“Header-Name: value”,以回車結尾。這個格式也被用于郵件等,更加詳細的說明:
請求頭是區分大小寫的
冒号‘:’後面可以有任意多個空格
下面的兩種格式,效果是一樣的:
HTTP1.0定義了16種頭,沒有強制要帶的。而HTTP1.1定義了46種頭,并且請求時必須帶(Host:)。請求時,一些約定俗稱的規定(不遵守沒問題,但是最好遵守)。
From頭:包含誰請求的,或者這個操作做了什麼。
User-Agent:它包含了誰請求的資訊(使用者身份),例如:<code>User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36</code>
上面說的這些頭,幫助網絡管理者分析問題。這些資訊也提供了使用者的身份(這些資訊可以僞造)。
如果你在寫一個‘servers’,考慮傳回響應時把下面這些頭加上:
Server:類似User-Agent頭,表示的是服務的身份。
Last-Modified:記此檔案在服務期端最後被修改的時間。通常用于緩存,節省帶寬。例如:<code>Last-Modified: Fri, 31 Dec 1999 23:59:59 GMT</code>
一個HTTP消息,頭資訊後面可能有一個消息實體。響應的時候,這個消息實體就是:用戶端請求的資源,或者是提示的錯誤資訊。請求時,消息實體就是:使用者輸入的資料,或者上傳的檔案。
如果一個HTTP消息包含消息實體,那麼通常就會下面的幾個頭,用來描述消息實體,例如:
Content-type: 用來表示消息實體的資料類型例如:‘text/html’或者‘image/gif’
Content-Length: 表示消息體的大小(bytes)
例如請求一個檔案:<code>http://www.somehost.com/path/file.html</code>
首先與目标網站:'www.somehost.com'的80端口建立起socket連接配接。之後通過socket連接配接發送類似:
這時伺服器回傳回響應,内容格式如下:
發送完響應之後,伺服器會關閉這個socket連接配接。
HTTP代理就是伺服器和用戶端的一個中間程式。它從用戶端接受請求,然後把這些請求再發送給伺服器。響應傳回的時候也是同理,需要通過代理。
代理通常用于防火牆,區域網路的安全等。
當用戶端使用代理,它就會把所有的請求發送給代理,而不是發給伺服器。通過代理請求和普通的請求有一點不同:第一行,代理請求使用完整的URL,而不是隻有path。例如:
通過這種方式,代理就知道請求的伺服器位址了。
就像常說的:“嚴格的發送,寬容的接收。”你互動資訊過程中,其它的用戶端和伺服器,它們發送的資訊都有可能有瑕疵。但是,你應該嘗試預料到這些問題,進而使一切正常的運作。下面有些建議:
即使規定必須以回車(CRLF)結尾,但是一些人可能隻用換行(LF),是以請同時接受這兩者。
發送的消息内部,每一個部分必須由一個空行進行隔離。但是,其它程式有可能使用幾個空行來隔離。是以也一定要考慮接受這種情況。
當然還有一些其他的情況,總之要多相容。
這是HTTP的基本知識。如果你想知道更多,你需要檢視官方的資料。
到此為止隻講理HTTP1.0的知識,下面會講HTTP1.1的知識,是以休息一下,讓我們更新一下!
HTTP1.1
想很多的協定一樣,HTTP是不斷更新的。HTTP1.1完善了HTTP1.0的一些缺點。總的來說,改善的地方包括:
更快的響應,在一個連接配接上允許多個HTTP的請求和響應。(叫做:HTTP持久連接配接)
增加緩存的支援,節省了帶寬,提高了響應的速度。
更快的響應和生成頁面,因為支援分塊編碼,允許發送的資料可以分成多個部分,好處是:發送資料之前,不需要預先知道發送内容的總大小。
因為增加了Host頭字段,web浏覽器可以使用一個IP位址配置多個虛拟web站點。
HTTP1.1需要再伺服器和用戶端增加一些額外的東西。接下來的兩個部分,分别講:如何編寫遵循HTTP1.1協定的用戶端和伺服器。當然,你如果隻寫用戶端,隻需要看用戶端的部分。可以根據自己的需求選擇閱讀。
為了符合HTTP1.1,用戶端必須:
每次請求必須包含Host頭。
允許響應是chunked data(分塊傳輸編碼)。
每個請求,必須在頭資訊中,聲明是否支援持久連接配接。
支援響應傳回狀态碼:‘100 Continue’。
HTTP1.1開始,支援一個IP對應多個虛拟主機。比如:“www.host1.com”和“www.host2.com”可以是同一個伺服器(同一IP)。
一個伺服器,有多個域名就像:不同的人,共享一個手機。打電話的人知道他們要找誰,但是接電話的人不知道!是以,打來電話的人需要明确的指出他要找誰。同理,每一個HTTP請求必須在Host頭中,明确地指出請求的host。例如:
其中<code>:80</code>不需特别指出,因為預設就是通路80端口。
HTTP1.1協定下的請求,請求頭中必須包含<code>Host</code>。沒有它,每一個域名需要一個獨一無二的IP位址,IP位址的資料量正在急劇減少,而網站(域名)卻以爆炸的速度增長。Host可以有效的緩解IP位址緊張的現狀。
如果伺服器想要在知道響應資料總量之前,就發送響應(比如特别長的響應内容,這樣計算資料總量會耗費很長的時間),那麼就會用到‘分塊傳輸編碼’。它把完整的響應資料,分成很多個大小相同的資料塊,然後發送。你可以同樣接收這樣的資料,因為頭已經包含了‘Transfer-Encoding: chunked’。所有的HTTP1.1用戶端必須可以接收分塊的資訊。
分塊的消息内容需要包含:有一行是‘0’,用于表示内容的結束。有'footers',和一個空行。必須包含兩個部分:
有一行是用16進制表示這個塊的大小,後面的額外參數用分号隔離。
資料以回車分割。
例如:
不要忘記,最後有一行空行。文本内容的大小是42bytes(1a+10=16+10+16=42),内容是:‘abcdefghijklmnopqrstuvwxyz1234567890abcdef’。
分塊資料可以包含任意的二進制資料。下面内容是一樣的,但是沒有使用‘分塊傳輸編碼’的響應。如下:
在HTTP1.0之前,每個請求和響應完成後都會關閉TCP連接配接,是以每次擷取都是一個單獨,獨立的連結。建立和關閉TCP連接配接花費大量的CPU資源,帶寬和記憶體。實際操作中,組成一個網頁的多個檔案都是在一台伺服器上。是以,多個請求和響應都可以通過一個持久連接配接進行傳輸。
HTTP1.1預設是持久連接配接,是以如果沒有特殊的需求使用的就是持久連接配接。隻需要建立一個連接配接,就是可以發送多個請求和讀取傳回的響應。如果你這麼做,一定要注意讀取響應傳回的長度,以確定正确的差別他們。
如果一個用戶端在請求頭中聲明了“Connection: close”的話,連接配接會在響應送達後關閉。比如,這種操作的場景:如果你知道這是這個連接配接的,最後一個請求。同樣的,如果響應頭包含這個聲明,伺服器會在發送完響應之後,關閉連接配接。是以,用戶端不能通過這個連結,發送任何請求。
伺服器可能在發送任何一個響應之前關閉連接配接。是以,用戶端必須保持時刻檢查Persistent Connections頭的值。以確定選擇的連接配接是通路。
用戶端使用HTTP1.1協定向伺服器發送請求,伺服器可能傳回臨時響應:‘100 Coninue’。它表示伺服器接收到了請求的第一部分,後面還有一些緩慢的資料傳輸。是以,無論如何HTTP1.1的用戶端必須能正确處理‘100’狀态碼的響應。
傳回的‘100 Continue’狀态碼,和我們前面說的‘200 OK’都是一樣的,符合正常的響應的格式。唯一不同的是,響應的内容。如下:
為了解決上面的這種情況(100 狀态碼沒有資料),一個簡單的HTTP1.1用戶端可以通過socket讀取響應;如果狀态碼是100,就忽略這次響應,轉而讀取下個響應。
為了遵從HTTP1.1,伺服器必須:
從用戶端的請求中得到host頭。
接受絕對url的請求。
可以接收分塊傳輸編碼。
支援‘持續連接配接’。
恰當的使用‘100 Continue’。
每個響應中包含‘Date’頭。
能夠處理‘If-Modified-Since’和‘If-Unmodified-Since’頭。
最起碼要支援‘GET’和‘HEAD’方法。
相容HTTP1.0的請求。
每個請求,必須包含Host頭,否則就會傳回‘400 Bad Request’響應,如下:
Host頭實際上是一個過渡的解決差別host的辦法。以後的HTTP版本,請求将要使用絕對位址代替路徑,比如:<code>GET http://www.somehost.com/path/file.html HTTP/1.2</code>
HTTP1.1伺服器必須接受這種格式的請求,盡管HTTP1.1用戶端不發送這樣的請求。如果用戶端沒有host頭,伺服器還必須要報告錯誤。
就像HTTP1.1客戶必須接受分塊的響應,伺服器必須接受分塊的請求。伺服器不需要生成,分塊的資訊。隻要能夠接受分塊請求就可以了。
如果HTTP1.1用戶端通過一個連接配接,發送了多個請求。為了支援持久連接配接,伺服器傳回響應的順序應該和請求的順序是一樣的。
如果過一個請求包含‘Connection: close’頭,表示這是這個連接配接的最後一個請求,伺服器需要在傳回響應後關閉連接配接。伺服器也會關閉逾時閑置的連接配接。(通常設定10s逾時)
如果你不想支援持久連接配接,響應頭中包含‘Connection: close’。這就是表示:傳回目前這個響應之後,連接配接就會關閉。正确的支援HTTP1.1用戶端能正确的接受這個頭資訊。
正如HTTP1.1用戶端那段描述的那樣,這個響應是為了處理反應慢的連接配接的。
當一個HTTP1.1伺服器收到一個HTTP1.1請求,如果不是傳回‘100 Continue’就是錯誤代碼。如果它發送‘100 Continue’響應,那麼接下來服務還會發送另外一個響應。‘100 Continue’不需要頭,但是必須含有空行。如下:
不要向HTTP1.0用戶端發送‘100 Continue’
緩存是HTTP1.1的一個重大改善,同時離不開響應的時間戳。是以,伺服器傳回的每個響應,必須包含Date頭,表示目前時間。格式如下:<code>Date: Fri, 31 Dec 1999 23:59:59 GMT</code>
除了‘1xx’的狀态碼的響應,所有的響應必須包括Date頭。時間同一用:格林威治标準時間表示。
避免發送不必要的資源,這樣就節省了帶寬。HTTP1.1定義了‘If-Modified-Since’和‘If-Unmodified-Since’請求頭。用于表示:“隻有在這個時間之後修改過的才發送”;用戶端不需這些,但是HTTP1.1需要這些資訊。
不幸的是,早期的HTTP版本,時間值的格式不統一,例如:
是以這次,HTTP統一使用格林威治标準時間表示。
盡管伺服器必須接受三種時間格式,HTTP1.1用戶端和伺服器隻能生成一種時間格式。如果沒有這個頭,請求将傳回不成功的狀态碼。
If-Modified-Since頭被用在GET請求上。如果請求資源在給的這段時間修改過,忽略這個頭,正常的傳回資源。否則傳回‘304 Not Modified’響應,包含Date頭,同時沒有消息實體。比如:
If-Unmodified-Since頭和If-Modified-Since頭是相似的,但是不能用于任何方法。指定的請求資源隻有在字段值内指定的日期時間之後,未發生更新的情況下,才能處理請求。如果在指定日期時間後發生更新,則以狀态碼<code>412 Precondition Failed</code>作為響應傳回。例如:
HTTP1.1伺服器必須支援GET和HEAD方法。如果你正在使用CGI腳本,你需要也支援POST方法。
HTTP1.1定義的其它四個方法(PUT, DELETE, OPTIONS, and TRACE),它們不經常被用到。如果用戶端請求的方法,伺服器不支援,則傳回‘501 Not Implemented’,如下:
為了相容老的浏覽器,HTTP1.1伺服器必須支援HTTP1.0請求。當一個請求使用HTTP1.0:
不需要Host頭
不能發‘100 Continue’響應
這個系列的文章全部翻譯完成,分别是:CGI真的很簡單和HTTP真的很簡單。但是我覺得還是有些欠缺的地方,應該在寫一個socket真的很簡單的文章,因為這些都是建立在socket通信的基礎上的。我接下來的計劃是這樣的:
先通過一個實戰,把這兩個知識點融會貫通,真正掌握。
最後,視情況。補充完成整個系列的文章。我覺得要寫的東西還有很多很多!