總結了自己在實際工作場景中遇到的與http協議相關的一些內容的理解。
Request & Response
Request格式
<request-line> 比如:GET /api/index.json HTTP/1.1
<headers> 比如:Accept: */*; User-Agent: Mozilla/4.0;……
<blank line>
[<request-body>] 比如:id=1×tamp=xxxxxx
Response格式
<status-line> 比如:HTTP/1.1 200 OK
<headers> 比如:Content-Type: application/json;……
<blank line>
[<response-body>] 比如:{"id":1,"username":"testuser"}
Status Code
http的狀態碼有將近60個,我這裡主要記錄一些常見的非正常情況下產生的狀態碼,在平常應用中或多或少會碰到,有助於我們去理解和發現問題。
206 - 斷點下載時用到,客戶端請求了一部分內容,伺服器成功把這部分內容返回給它,這時候就是用這個狀態。
301 - 永久跳轉,原地址不存在了,url被指向到另一個地址。這個主要是搜尋引擎相關,影響爬蟲的檢索行為。
302 - 臨時跳轉,伺服器會返回一個新的url給客戶端,客戶端可以繼續訪問這個url來獲取內容。
304 - 資源沒有改變,客戶端可以使用本地快取的內容,常見於靜態內容訪問。
413 - 請求實體太大。常見的情況是上傳大檔案,但超出了伺服器(比如nginx)限制。或者請求頭或請求體超出了後端的server(比如tomcat)的設定(比如當前域名下cookie太多,超出了請求頭限制)
416 - 跟斷點續傳有關,客戶端請求的範圍超出了伺服器上檔案大小。
500 - 伺服器內部錯誤,不能返回正常的結果。比如最常見的應用丟擲空指標異常未進行處理。
502 - 閘道器錯誤。常見的情況是反向代理後端的伺服器(比如resin或tomcat)沒有啟動。
503 - 服務不可用。比如伺服器負載太高或者伺服器已經停止服務。
504 - 閘道器超時。比如請求時長超出了伺服器的響應時間限制。
Headers
http headers分為請求頭(Request Header)和響應頭(Response Header)兩類。下面是我們經常用到的一些header.
1.快取控制
在網際網路站的應用中,快取幾乎無處不在,在基於http的服務中,我們也可以對一些不常改變的內容在客戶端進行快取,這樣在多次訪問中可以複用快取內容,加快訪問速度,提升使用者體驗。http的協議裡規定了一些用於快取控制的http訊息頭:
Cache-Control(HTTP/1.1)/Pragma(HTTP/1.0):指示客戶端是否進行快取以及快取的時間是多長。預設值是private,也就是把內容快取在使用者私有空間。比如:Cache-Control:max-age=86400,must-revalidate,這是告訴客戶端所請求的資源快取一天(max-age單位是秒,相對時間),過期之後必須進行重新檢驗。
Expires:指定客戶端(如果不強制重新整理的話)在多長時間裡可以不向伺服器發請求,直接讀本地快取。
注意:
優先順序:Cache-Control > Expires;
詳細引數說明:http://condor.depaul.edu/dmumaugh/readings/handouts/SE435/HTTP/node24.html
不同瀏覽器的不同行為(重新整理,後退,位址列回車等)在實現上可能有差異;
Last-Modified/If-Modified-Since:Last-Modified是伺服器端返回給客戶端的資源最後修改時間戳,這樣,客戶端在下次請求時(比如強制重新整理)會帶上If-Modified-Since引數來校驗資源是否有更新,沒有更新的話伺服器就返回304狀態碼,客戶端直接取本地快取的資源。這個時候只有請求開銷,沒有網路傳輸開銷。注意:時間戳必須是格林威治(GMT)時間,比如:Last-Modified:Sat, 19 Oct 2013 09:20:15 GMT
ETag/If-None-Match:ETag是根據檔案屬性透過一定演算法生成的資源標識,也是用來確定客戶端請求的資源是否有更新。如果伺服器返回了一個ETag值給客戶端,那麼下次客戶端請求時會帶上If-None-Match引數來校驗資源是否更新,沒有更新的就返回304狀態碼。(效果基本等同於Last-Modified)
注意:
ETag需要計算,對於計算資源緊張的伺服器來說是一種消耗,所以有些網站直接不使用ETag;
如果伺服器在負載均衡後面,同一個資源的請求可能分發到不同的後端機器上,由於ETag的計算依賴於檔案屬性,不同機器上內容相同的檔案可能生成的ETag不同,這樣就可能使本來內容沒變的檔案透過ETag校驗失敗。這裡有兩種解決方案:一是etag計算不依賴於本地機器,比如直接算檔案內容的md5值;二是在負載均衡器上把相同的url請求分發到同一臺後端機器。
在我們的實際業務場景下,http的快取具有非常大的用途,下面列舉一些:
充分利用客戶端的資源,比如一些客戶端需要頻繁訪問的靜態檔案,像LOGO,廣告圖等,完全可以快取在客戶端本地。這樣可以減少網路請求,加快客戶端展示,還能減少伺服器請求的壓力。
我們的一些靜態內容,比如新聞,部落格等,在被搜尋引擎爬蟲抓取的時候,透過控制快取引數,就可以減少爬蟲的抓取頻率,減少不必要的資源浪費。
如果我們的靜態資源使用了CDN,那麼設定了http快取就可以在CDN節點上儲存一份檔案,減少CDN的回源次數,減少網路延時和源站伺服器壓力。
2.斷點請求
Accept-Ranges:服務端支援斷點下載時會返回這個響應頭給客戶端,當客戶端知道這個以後就可以傳送斷點請求了。
Content-Length:響應資訊的長度,告訴客戶端當前請求返回了多少資料。這裡要注意一下,用head方法提交請求時不會返回具體資料,但是這個Content-Length會返回完整資料的大小。
Range/Content-Range:客戶端請求時提交名為Range的header,告訴伺服器自己要請求哪部分的資料。比如:Range: bytes=0-1023表示請求第0到1023個位元組.然後伺服器返回這1024個位元組的內容給客戶端,響應頭中會帶上Content-Range。即:Content-Range: bytes 0-1023/4096,這個4096就是檔案總大小。客戶端下次請求可以從第1024個位元組處開始,Range: bytes=1024-xxxx
3.編碼
Accept-Encoding/Content-Encoding:前者是客戶端支援接收的訊息編碼型別。預設是identity,可選值有gzip,compress等。後者是伺服器端響應資訊的內容編碼型別,常用的就是壓縮。壓縮的好處顯而易見,可以大大減少網路傳輸的開銷,相對於伺服器端壓縮產生的cpu消耗,網路傳輸的減少顯然更實在。常見形式:Content-Encoding: gzip,deflate,compress.通常我們對html,js,css,xml,json之類的響應結果可以進行壓縮傳輸。
Transfer-Encoding:response header.響應訊息的傳輸編碼型別,規定了網路傳輸的形式。一般都是下面這種形式:Transfer-Encoding: chunked。當伺服器產生動態內容,不知道響應資訊的具體長度時,可以透過這個指定分塊進行傳輸,處理多少資料就返回多少資料,這樣不用等到資料都準備好了一次性返回。結合上面的內容編碼,比如gzip,可以分塊壓縮並進行傳輸。另外,請注意,在使用這種編碼傳輸時,我們是看不到Content-Length的,因為內容還沒有完全生成。
4.其他
X-Forward-For:request header. 用來標識使用者的真實ip,特別是透過代理(正向或反向)訪問伺服器或是伺服器在負載均衡裝置後面的情況。格式:X-forward-For: client,proxy1,proxy2,…最左邊的是最接近客戶端的ip。
User-Agent:request header.伺服器用來識別客戶端基本資訊的請求頭。一般這個在識別搜尋爬蟲的時候有用,某些場景下也可以用這個來做一些客戶端的統計。
Referer:request header.客戶端訪問伺服器時,這個Referer來指定請求來源,比如是從哪個網站連結過來的,我們在一些統計中會經常用到這個。另外,還有一個重要的用途就是在需要資源防盜鏈的場景中來過濾非法的請求來源(但是,這個referer是客戶端可以偽造的)。
Location:response header.在301/302狀態碼的響應頭中,都會帶上這個Location頭,來指示客戶端用新的地址去訪問需要的資源。
Connection:request/response header.在http/1.1中,客戶端和服務端預設都是保持連線的,也就是Connection: keep-alive.如果任何一方不想保持連線,都可以把這個值設定為close.預設情況下,客戶端和服務端會保持一個長連線,這樣客戶端就可以用這個連線傳送多次http請求,減少頻繁建立連線帶來的消耗。對於這個引數,在服務端可能要做更多的設定,比如連線keep-alive的時間,伺服器核心的一些網路引數設定(針對tcp)。
Session和Cookie
http請求是無狀態的請求,但是在我們的網際網路應用中,經常需要標識使用者狀態資訊來完成一些互動性的操作,比如使用者認證要記錄使用者登入狀態,購物車應用要記住使用者選擇的商品,廣告投放應用要記錄使用者的歷史瀏覽行為等等。這裡就會用到session和cookie了。
session:是指http請求-響應的過程中客戶端與伺服器端的互動狀態,這些資訊被儲存在伺服器端,比如記憶體,資料庫等。每個session都有一個唯一標識,由伺服器生成,這個標識也要在客戶端進行儲存,這樣客戶端在下次請求時可以帶上這個標識,方便伺服器判斷客戶端的狀態。
客戶端對session的支援:
透過cookie儲存session id,在請求時傳送給伺服器。
透過url的引數攜帶session id與伺服器通訊。
透過表單的隱藏欄位攜帶session id與伺服器通訊。
session共享的問題:
在分散式應用中,我們的http server一般都架在反向代理或是負載均衡裝置後面,這就會面臨一個session共享的問題。也就是同一個使用者的多個請求可能被分發到多個不同的機器,如果我們把session儲存在機器本地記憶體中的話,就無法在多個機器間共享使用者的session。這個問題,一般來說,我們可以有兩種方式來解決:
把session存放到分散式的記憶體(eg:memcached)或是集中式儲存中(eg:database)。
在反向代理或負載均衡裝置上把相同使用者的請求分發到同一臺機器(這裡要處理好機器當機後請求重新分配的問題)。
cookie:在客戶端保持狀態化資訊,每個cookie內容都屬於特定的域(domain)和路徑(path),出於安全考慮,不同域或路徑下的cookie不能共享。
會話cookie:沒有指定過期時間,儲存在記憶體,瀏覽器關閉後就失效。
持久cookie:指定了過期時間,儲存在瀏覽器本地。
詳細內容可以參考:http://en.wikipedia.org/wiki/HTTP_cookie
需要注意的是cookie會存在一些安全方面的問題。
在這裡我只是總結了自己在工作中遇到的與http協議相關的一些內容的理解,http協議還有很多需要挖掘的東西,也需要不斷去探索,對http協議的理解將會給我們的開發應用帶來很大的便利。
最後,推薦兩個很NB的http除錯工具:fiddler(windows)和charles(mac)有http代理功能,對於不是基於瀏覽器的http應用(比如mobile app),可以用這兩個工具來監控http請求。