HTTP真的很簡單


原文: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 為例子演示一下。如下圖:
chrome

HTTP真的很簡單

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?

HTTP是超文本傳輸協議,是一種用於在萬維網,傳輸文件,無論是HTML文件,還是圖片,請求等數據的網絡協議。通常HTTP協議是建立在TCP/IP socket通信基礎之上的。

HTTP協議是客戶端(client)和服務器(servers)通信的協議。瀏覽器就是一個客戶端,因為它發送請求給HTTP服務器(也就是web服務器),web服務器返回響應給客戶端。符合HTTP協議的服務器,默認監聽80端口,當然也可以重新指定任何一個端口。

什么是資源?

HTTP協議是用來傳播資源,而不僅僅只是文件。資源就是一個URL鏈接所對應的一些信息。我們普遍見到的資源就是文件,同時資源也可能是:通過不同編程語言寫的CGI腳本,動態生成,並輸出。返回請求結果的文件。

學習HTTP,有助於理解資源類似於文件的概念。實際場景中,HTTP資源不是靜態文件,就是服務器端腳本動態生成的結果。

HTTP的傳輸結構

就像大多數的網絡協議,HTTP也是C/S模式:客戶端向服務器發送請求連接和請求的信息內容,服務器返回響應信息。通常包含請求的資源。服務器發送完響應后,關閉連接。(HTTP是一個無狀態的連接)

請求和響應的格式長得差不多,它們都是由:

  • 一條初始行
  • 零或多條頭信息
  • 一個空行
  • 一個可選的消息體
    組成的,格式如下,:
<initial line, different for request vs. response>
Header1: value1
Header2: value2
Header3: value3

<optional message body goes here, like file contents or query data;
 it can be many lines long, or even binary data $&*%@!^$@>

initial lineheaders必須由回車結尾。

Initial Request Line(請求行)

請求的第一行和響應的第一行不一樣!請求的第一行有三個部分:方法名,請求資源的路徑(也就是/分隔的路徑),使用的HTTP協議版本。訪問我的博客首頁時,請求頭如下:

GET / HTTP/1.1

注意:

  • GET方法是HTTP中最常用的方法,他的意思是:‘我要得到這個資源’。另外一個常用的方法是:POST后面會在做詳細的說明。方法名全部大寫。
  • 域名后面的部分就是路徑,默認是‘/’。
  • HTTP版本形如:‘HTTP/x.x’,全部大寫。

Initial Response Line(響應聲明行或狀態行)

響應的初始行,稱作‘狀態行’。也是由三個部分組成的:HTTP協議版本,狀態碼,狀態碼描述。同樣以我博客為例子,狀態行如下:

HTTP/1.1 304 Not Modified

注意

  • HTTP版本的內容,形式跟上面一樣。
  • 狀態碼是三位的整數,第一位通常分為如下幾類:
    • 1xx 這一類型的狀態碼,代表請求已被接受,需要繼續處理。(消息)
    • 2xx 這一類型的狀態碼,代表請求已成功被服務器接收、理解、並接受。(成功)
    • 3xx 這類狀態碼代表需要客戶端采取進一步的操作才能完成請求。(重定向)
    • 4xx 這類的狀態碼代表了客戶端看起來可能發生了錯誤,妨礙了服務器的處理。(客戶端錯誤)
    • 5xx 這類狀態碼代表了服務器在處理請求的過程中有錯誤或者異常狀態發生。(服務器錯誤)

常見的狀態碼:
200 OK:請求成功,接收到資源。
404 Not Found:請求失敗,未找到資源。
301 Moved Permanently:永久性轉移。
302 Moved Temporarily:暫時性轉移。
303 See Other:請求的資源已經移到了另外一個URL上了,客戶端會自動跳轉。這個通常是CGI腳本使用redirect,使得客戶端,重定向到另外一個URL。
500 Server Error:一個未知的服務器錯誤。

Header Lines(請求頭)

請求頭提供了請求或響應的信息,或者是關於發送的消息體(message body)的信息。

請求頭的格式為:每條頭信息占一行例如“Header-Name: value”,以回車結尾。這個格式也被用於郵件等,更加詳細的說明:

  • 請求頭是區分大小寫的
  • 冒號‘:’后面可以有任意多個空格

下面的兩種格式,效果是一樣的:

Header1: some-long-value-1a, some-long-value-1b

HEADER1:    some-long-value-1a,
            some-long-value-1b

HTTP1.0定義了16種頭,沒有強制要帶的。而HTTP1.1定義了46種頭,並且請求時必須帶(Host:)。請求時,一些約定俗稱的規定(不遵守沒問題,但是最好遵守)。

  • From頭:包含誰請求的,或者這個操作做了什么。
  • User-Agent:它包含了誰請求的信息(用戶身份),例如: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

上面說的這些頭,幫助網絡管理員分析問題。這些信息也提供了用戶的身份(這些信息可以偽造)。
如果你在寫一個‘servers’,考慮返回響應時把下面這些頭加上:

  • Server:類似User-Agent頭,表示的是服務的身份。
  • Last-Modified:記此文件在服務期端最后被修改的時間。通常用於緩存,節省帶寬。例如:Last-Modified: Fri, 31 Dec 1999 23:59:59 GMT

Message Body(消息實體)

一個HTTP消息,頭信息后面可能有一個消息實體。響應的時候,這個消息實體就是:客戶端請求的資源,或者是提示的錯誤信息。請求時,消息實體就是:用戶輸入的數據,或者上傳的文件。

如果一個HTTP消息包含消息實體,那么通常就會下面的幾個頭,用來描述消息實體,例如:

  • Content-type: 用來表示消息實體的數據類型例如:‘text/html’或者‘image/gif’
  • Content-Length: 表示消息體的大小(bytes)

HTTP交互的例子

例如請求一個文件:http://www.somehost.com/path/file.html

首先與目標網站:'www.somehost.com'的80端口建立起socket連接。之后通過socket連接發送類似:

GET /path/file.html HTTP/1.0
From: someuser@jmarshall.com
User-Agent: HTTPTool/1.0
[blank line here]

這時服務器回返回響應,內容格式如下:

HTTP/1.0 200 OK
Date: Fri, 31 Dec 1999 23:59:59 GMT
Content-Type: text/html
Content-Length: 1354

<html>
<body>
<h1>Happy New Millennium!</h1>
(more file contents)
  .
  .
  .
</body>
</html>

發送完響應之后,服務器會關閉這個socket連接。

HTTP代理

HTTP代理就是服務器和客戶端的一個中間程序。它從客戶端接受請求,然后把這些請求再發送給服務器。響應返回的時候也是同理,需要通過代理。

代理通常用於防火牆,局域網的安全等。

當客戶端使用代理,它就會把所有的請求發送給代理,而不是發給服務器。通過代理請求和普通的請求有一點不同:第一行,代理請求使用完整的URL,而不是只有path。例如:

通過代理請求:GET http://www.somehost.com/path/file.html HTTP/1.0
普通請求:GET /path/file.html HTTP/1.0

通過這種方式,代理就知道請求的服務器地址了。

‘寬容待人’

就像常說的:“嚴格的發送,寬容的接收。”你交互信息過程中,其它的客戶端和服務器,它們發送的信息都有可能有瑕疵。但是,你應該嘗試預料到這些問題,從而使一切正常的運行。下面有些建議:

  • 即使規定必須以回車(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 Clients(客戶端)

為了符合HTTP1.1,客戶端必須:

  • 每次請求必須包含Host頭。
  • 允許響應是chunked data(分塊傳輸編碼)。
  • 每個請求,必須在頭信息中,聲明是否支持持久連接。
  • 支持響應返回狀態碼:‘100 Continue’。

Host Header(Host頭)

HTTP1.1開始,支持一個IP對應多個虛擬主機。比如:“www.host1.com”和“www.host2.com”可以是同一個服務器(同一IP)。

一個服務器,有多個域名就像:不同的人,共享一個手機。打電話的人知道他們要找誰,但是接電話的人不知道!所以,打來電話的人需要明確的指出他要找誰。同理,每一個HTTP請求必須在Host頭中,明確地指出請求的host。例如:

GET /path/file.html HTTP/1.1
Host: www.host1.com:80
[blank line here]

其中:80不需特別指出,因為默認就是訪問80端口。
HTTP1.1協議下的請求,請求頭中必須包含Host。沒有它,每一個域名需要一個獨一無二的IP地址,IP地址的數據量正在急劇減少,而網站(域名)卻以爆炸的速度增長。Host可以有效的緩解IP地址緊張的現狀。

分塊傳輸編碼

如果服務器想要在知道響應數據總量之前,就發送響應(比如特別長的響應內容,這樣計算數據總量會耗費很長的時間),那么就會用到‘分塊傳輸編碼’。它把完整的響應數據,分成很多個大小相同的數據塊,然后發送。你可以同樣接收這樣的數據,因為頭已經包含了‘Transfer-Encoding: chunked’。所有的HTTP1.1客戶端必須可以接收分塊的信息。

分塊的消息內容需要包含:有一行是‘0’,用於表示內容的結束。有'footers',和一個空行。必須包含兩個部分:

  • 有一行是用16進制表示這個塊的大小,后面的額外參數用分號隔離。
  • 數據以回車分割。

例如:

HTTP/1.1 200 OK
Date: Fri, 31 Dec 1999 23:59:59 GMT
Content-Type: text/plain
Transfer-Encoding: chunked

1a; ignore-stuff-here
abcdefghijklmnopqrstuvwxyz
10
1234567890abcdef
0
some-footer: some-value
another-footer: another-value
[blank line here]

不要忘記,最后有一行空行。文本內容的大小是42bytes(1a+10=16+10+16=42),內容是:‘abcdefghijklmnopqrstuvwxyz1234567890abcdef’。

分塊數據可以包含任意的二進制數據。下面內容是一樣的,但是沒有使用‘分塊傳輸編碼’的響應。如下:

HTTP/1.1 200 OK
Date: Fri, 31 Dec 1999 23:59:59 GMT
Content-Type: text/plain
Content-Length: 42
some-footer: some-value
another-footer: another-value

abcdefghijklmnopqrstuvwxyz1234567890abcdef

Persistent Connections(持久連接)

在HTTP1.0之前,每個請求和響應完成后都會關閉TCP連接,所以每次獲取都是一個單獨,獨立的鏈接。創建和關閉TCP連接花費大量的CPU資源,帶寬和內存。實際操作中,組成一個網頁的多個文件都是在一台服務器上。所以,多個請求和響應都可以通過一個持久連接進行傳輸。

HTTP1.1默認是持久連接,所以如果沒有特殊的需求使用的就是持久連接。只需要建立一個連接,就是可以發送多個請求和讀取返回的響應。如果你這么做,一定要注意讀取響應返回的長度,以確保正確的區別他們。

如果一個客戶端在請求頭中聲明了“Connection: close”的話,連接會在響應送達后關閉。比如,這種操作的場景:如果你知道這是這個連接的,最后一個請求。同樣的,如果響應頭包含這個聲明,服務器會在發送完響應之后,關閉連接。所以,客戶端不能通過這個鏈接,發送任何請求。

服務器可能在發送任何一個響應之前關閉連接。所以,客戶端必須保持時刻檢查Persistent Connections頭的值。以確保選擇的連接是通路。

100 Connections(100狀態碼)

客戶端使用HTTP1.1協議向服務器發送請求,服務器可能返回臨時響應:‘100 Coninue’。它表示服務器接收到了請求的第一部分,后面還有一些緩慢的數據傳輸。所以,無論如何HTTP1.1的客戶端必須能正確處理‘100’狀態碼的響應。

返回的‘100 Continue’狀態碼,和我們前面說的‘200 OK’都是一樣的,符合正常的響應的格式。唯一不同的是,響應的內容。如下:

HTTP/1.1 100 Continue   #沒有過完整的響應內容。

HTTP/1.1 200 OK
Date: Fri, 31 Dec 1999 23:59:59 GMT
Content-Type: text/plain
Content-Length: 42
some-footer: some-value
another-footer: another-value

abcdefghijklmnoprstuvwxyz1234567890abcdef

為了解決上面的這種情況(100 狀態碼沒有數據),一個簡單的HTTP1.1客戶端可以通過socket讀取響應;如果狀態碼是100,就忽略這次響應,轉而讀取下個響應。

HTTP1.1服務器

為了遵從HTTP1.1,服務器必須:

  • 從客戶端的請求中得到host頭。
  • 接受絕對url的請求。
  • 可以接收分塊傳輸編碼。
  • 支持‘持續連接’。
  • 恰當的使用‘100 Continue’。
  • 每個響應中包含‘Date’頭。
  • 能夠處理‘If-Modified-Since’和‘If-Unmodified-Since’頭。
  • 最起碼要支持‘GET’和‘HEAD’方法。
  • 兼容HTTP1.0的請求。

需要Host頭

每個請求,必須包含Host頭,否則就會返回‘400 Bad Request’響應,如下:

HTTP/1.1 400 Bad Request
Content-Type: text/html
Content-Length: 111

<html><body>
<h2>No Host: header received</h2>
HTTP 1.1 requests must include the Host: header.
</body></html>

接受絕對地址

Host頭實際上是一個過渡的解決區別host的辦法。以后的HTTP版本,請求將要使用絕對地址代替路徑,比如:GET http://www.somehost.com/path/file.html HTTP/1.2

HTTP1.1服務器必須接受這種格式的請求,盡管HTTP1.1客戶端不發送這樣的請求。如果客戶端沒有host頭,服務器還必須要報告錯誤。

分塊傳輸編碼

就像HTTP1.1客戶必須接受分塊的響應,服務器必須接受分塊的請求。服務器不需要生成,分塊的信息。只要能夠接受分塊請求就可以了。

持久連接

如果HTTP1.1客戶端通過一個連接,發送了多個請求。為了支持持久連接,服務器返回響應的順序應該和請求的順序是一樣的。

如果過一個請求包含‘Connection: close’頭,表示這是這個連接的最后一個請求,服務器需要在返回響應后關閉連接。服務器也會關閉超時閑置的連接。(通常設置10s超時)

如果你不想支持持久連接,響應頭中包含‘Connection: close’。這就是表示:返回當前這個響應之后,連接就會關閉。正確的支持HTTP1.1客戶端能正確的接受這個頭信息。

100 Coninue

正如HTTP1.1客戶端那段描述的那樣,這個響應是為了處理反應慢的連接的。

當一個HTTP1.1服務器收到一個HTTP1.1請求,如果不是返回‘100 Continue’就是錯誤代碼。如果它發送‘100 Continue’響應,那么接下來服務還會發送另外一個響應。‘100 Continue’不需要頭,但是必須含有空行。如下:

HTTP/1.1 100 Continue
[blank line here]
[another HTTP response will go here]

不要向HTTP1.0客戶端發送‘100 Continue’

Date頭

緩存是HTTP1.1的一個重大改善,同時離不開響應的時間戳。所以,服務器返回的每個響應,必須包含Date頭,表示當前時間。格式如下:Date: Fri, 31 Dec 1999 23:59:59 GMT

除了‘1xx’的狀態碼的響應,所有的響應必須包括Date頭。時間同一用:格林威治標准時間表示。

If-Modified-Since和If-Unmodified-Since頭

避免發送不必要的資源,這樣就節省了帶寬。HTTP1.1定義了‘If-Modified-Since’和‘If-Unmodified-Since’請求頭。用於表示:“只有在這個時間之后修改過的才發送”;客戶端不需這些,但是HTTP1.1需要這些信息。

不幸的是,早期的HTTP版本,時間值的格式不統一,例如:

If-Modified-Since:  Fri, 31 Dec 1999 23:59:59 GMT
If-Modified-Since:  Friday, 31-Dec-99 23:59:59 GMT
If-Modified-Since:  Fri Dec 31 23:59:59 1999

所以這次,HTTP統一使用格林威治標准時間表示。

盡管服務器必須接受三種時間格式,HTTP1.1客戶端和服務器只能生成一種時間格式。如果沒有這個頭,請求將返回不成功的狀態碼。

If-Modified-Since頭被用在GET請求上。如果請求資源在給的這段時間修改過,忽略這個頭,正常的返回資源。否則返回‘304 Not Modified’響應,包含Date頭,同時沒有消息實體。比如:

HTTP/1.1 304 Not Modified
Date: Fri, 31 Dec 1999 23:59:59 GMT
[blank line here]

If-Unmodified-Since頭和If-Modified-Since頭是相似的,但是不能用於任何方法。指定的請求資源只有在字段值內指定的日期時間之后,未發生更新的情況下,才能處理請求。如果在指定日期時間后發生更新,則以狀態碼412 Precondition Failed作為響應返回。例如:

HTTP/1.1 412 Precondition Failed
[blank line here]

支持GET和HEAD方法

HTTP1.1服務器必須支持GET和HEAD方法。如果你正在使用CGI腳本,你需要也支持POST方法。

HTTP1.1定義的其它四個方法(PUT, DELETE, OPTIONS, and TRACE),它們不經常被用到。如果客戶端請求的方法,服務器不支持,則返回‘501 Not Implemented’,如下:

HTTP/1.1 501 Not Implemented
[blank line here]

支持HTTP1.0請求

為了兼容老的瀏覽器,HTTP1.1服務器必須支持HTTP1.0請求。當一個請求使用HTTP1.0:

  • 不需要Host頭
  • 不能發‘100 Continue’響應

結尾

這個系列的文章全部翻譯完成,分別是:CGI真的很簡單和HTTP真的很簡單。但是我覺得還是有些欠缺的地方,應該在寫一個socket真的很簡單的文章,因為這些都是建立在socket通信的基礎上的。我接下來的計划是這樣的:

  • 先通過一個實戰,把這兩個知識點融會貫通,真正掌握。
  • 最后,視情況。補充完成整個系列的文章。我覺得要寫的東西還有很多很多!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM