HTTP協議基礎(HTTP 1.1)
一、http特點
1、客戶端/服務端模式
http協議是典型的服務端客戶端模式,一切http請求都是由客戶端發起,服務端只能被動接受。瀏覽器是很大的一類客戶端,這種一般也叫作b/s,可以說http既能支援傳統的c/s,也支援b/s。不過因為服務端不能主動發起通訊請求,所以http在開發即時應用時有先天不足,html5的websocket和新出的http2.0中的server push功能是對這一重大缺陷的改進。
2、無狀態
http本身對事務無記憶能力,本身不儲存通訊上下文,也就是這一次通訊不知道前一次通訊的結果,幾次通訊之間本身可以不干擾。這樣做的好處是伺服器不用管之前的通訊,可以應答地很快,同時能夠很簡單的擴充套件服務端,缺點就是單獨靠http無法完成複雜的邏輯。
要解決http本身不維持狀態的問題,需要依靠自定義的協議,這種協議一般很簡單,主要是在http報文中加一個唯一標識,同時依賴這個唯一標識儲存一些會話資訊。這個標識叫法有很多,比如sessionId、token等等,它們的基本作用是差不多的,就是標識請求方的綜合身份(這個綜合身份需要具體業務解釋);具體的會話資訊理論上雙方都可以儲存,但是現在為了安全,一般都由服務端儲存。
3、弱連線(連線可主動斷開)
http1.1之前,每次通訊完成後都有斷開連線,這樣影響了傳輸效率。http1.1引入Connection: keep-alive,並作為預設屬性,使得建立一次tcp可以進行多次http通訊。雖然有keep-alive,但是仍然不能認為http連線是長期可靠的,客戶端服務端都可以主動斷開連線的。通常的做法是客戶端在最後一次請求時攜帶Connection: close主動要求斷開,服務端是一段時間無請求就主動斷開。一般的基於原生tcp的應用都會想法設法維持連線,絕大多數時候連線斷開屬於異常行為,而http協議在通訊完成時斷開連線是合理也是很正常的。
4、靈活簡單
http對具體body報文格式如何解釋並不作規定,可以方便地傳輸各種格式的報文,http本身對tcp進行了很好的封裝,同時它的無狀態、弱連線的特性,讓大家在使用http協議通訊時基本不用關心tcp層的情況,開發者無需瞭解很多網路程式設計的知識即可快速有效地使用http。
二、Http Request
1、Http Request 基本格式
上圖中標紅的符號說明:
SP:代表白空格,也就是ASCII的0x20
CRLF:代表\r\n,也就是回車換行,\r是ASCII中的0x0d,\n是ASCII中的0x0a
COLON:代表英文冒號,也就是ASCII中的0x3a
2、Http Method
基於rfc2616的HTTP1.1規定了8個方法,分別是:
GET:rfc2616建議get做成冪等的、只讀安全的、用於獲取伺服器資料的方法,實際情況中GET主要被用在獲取網頁靜態資料、查詢類介面、jsonp請求中,算是一個萬能請求。
POST:用來向伺服器提交資料,這個定義很寬泛,給了服務端很大的主動性,因此POST實際中也是一個萬能的請求。最常見的是使用POST進行提交資料伺服器寫操作,一些webapi設計上也有使用POST提交json/xml等資料進行查詢功能的,這與設計有關。
本人的上一篇博文說了一些GET/POST之間的容易被誤傳的區別,這裡說下實際中的一些區別,在某些功能的設計上需要考慮這些區別:
a、GET是大多數瀏覽器預設請求;
b、GET所需要傳遞的全部資訊都在URL上,這意味著你可以手敲GET,還可以把URL發給別人,這比POST是個很大的優勢(不帶body的POST就不要用了);
c、GET只識uri規範支援的字元,傳遞其他字元需要轉碼,常見的有URLEncoder和用於URL的Base64編碼;
d、瀏覽器基本上都支援GET快取但是基本都不快取POST,這意味著GET可以被存為書籤,可以真正地有歷史記錄;
e、jsonp只支援GET;
f、GET很好被爬蟲,能夠被主流的搜尋引擎收錄,這對很多網站很重要。上面的不全是技術原因,很多時候是出於實用性和商業的考慮,實際中我建議是,如果在技術、安全、程式設計方便等指標上GET/POST無區別,還是用GET好;整體功能邏輯上的寫操作都使用POST操作,有跨域時優先使用服務端的CORS相關設定,這樣服務端有主動性,更好進行有效的安全控制。
HEAD:HEAD方法和GET方法的行為一致,但是HEAD方法能返回body部分,也就是隻返回狀態行和響應頭的GET方法。
PUT:我個人對rfc2616中PUT的理解是它相當於資料庫中的INSERT,具體更像REPLACE,不過服務端誰會讓你隨便建立資料/資源,因此PUT是不太常見的,很多情況下都被POST替代了。
DELETE:相當於資料庫的DELETE,但是和PUT一樣,被服務端限制了,基本是被POST替代了。
TRACE:用於http迴環測試,也就是客戶端傳送什麼,服務端就回應什麼。TRACE方法讓客戶端知道了服務端接收到的具體資料,原本是用來診斷除錯http問題的,但是很容易被黑客用來發現伺服器的問題從而惡意利用或者發起攻擊,現在的伺服器一般都禁止TRACE請求,或者重定向為GET等請求。
CONNECT:HTTP 1.1新增的用於連線HTTP代理的方法,正常情況比較少碰到CONNECT方法。
關於http方法的其他:
a、使用過fiddler的朋友可能會發現,fiddler模擬請求時的http方法不止上面八種,這些多出來的方法是WebDAV(Web-based Distributed Authoring and Versioning,一種基於http1.1的擴充套件協議)中定義的新方法,並不http規範中原生的方法,也不是所有伺服器瀏覽器都支援的。實際上http是基於tcp的文字協議,很容易自定義方法擴充套件http協議,這樣做基本上也不用更改整個的http協議解析過程。
b、最常見的http方法是get和post,不過最近隨著restful的webapi越來越被看重,PUT/DELETE這兩種方法也逐漸被正常支援並使用,這四種方法分別對應著webapi中的功能意義上的增(PUT)、刪(DELETE)、改(POST)、查(GET)。
c、對於http方法,建議是如果沒必要使用這個方法,就在http伺服器上禁止這個方法,一個http資源或者介面提供的http方法應該在滿足需求的情況下儘量少。
d、HTTP方法在很多實現中是區分大小寫的,碰見錯誤的大小寫,tomcat7會直接返回”501 Not Implemented”,vertx會識別為”OTHER”方法,全部大寫才是規範方法名。這一點也不要覺得奇怪的,通常的http實現中把請求行一起提取出來是最直接的,請求行中uri只是主機域名不區分大小寫,別的還是區分大小寫的,一般的普通請求的請求行中是沒有主機域名的。
3、Http URI
這裡就不討論URI和URL的區別了,這東西討論起來還很麻煩,uri的定義在rfc3986中,網上問這個問題的很多,可以看下這個問題,或者baidu google搜下相關的也行。
先說下uri的格式,rfc7230的5.3節規定了四種格式:源路徑格式(origin-form,或者叫伺服器上的絕對路徑格式)、絕對格式(absolute-form)、認證格式(authority-form,只用於CONNECT方法),星號格式(asterisk-form,只用於OPTIONS方法),這裡說下前面兩種。
比如請求http://192.168.4.222:12345/upload?arg=1,2
源路徑格式的http請求傳送的請求行是這個:GET /upload?arg=1,2 HTTP/1.1
相關屬性如下(使用的是vertx web)
request.absoluteURI() = “http://192.168.4.222:12345/upload?arg=1,2”
request.uri() = “/upload?arg=1,2”
request.query() = “arg=1,2”
request.path() = “/upload”
絕對格式的URI的請求行是:GET http://192.168.4.222:12345/upload?arg=1,2 HTTP/1.1
相關屬性如下(使用的是vertx web)
request.absoluteURI() = “http://192.168.4.222:12345http://192.168.4.222:12345/upload?arg=1,2”
request.uri() = “http://192.168.4.222:12345/upload?arg=1,2”
request.query() = “arg=1,2”
request.path() = “/upload”
可見這種絕對格式的uri對現在的一些http服務程式的某些api有影響。
目前大多數http客戶端在傳送普通http請求是都是使用的源路徑格式的uri,現在請求行中使用絕對格式uri一般用於對http代理伺服器發起請求。不過根據rfc7230的5.3節內容,說是要求http伺服器在以後要支援絕對格式的uri。
這裡再說幾點常用的知識:
a、http uri沒有定義最大長度,長度限制是客戶端和服務端自己定的;
b、http源路徑格式的uri 必須以”/”開頭,對http://www.abcd.com發起http請求,實際請求的URL是http://www.abcd.com/,URI是”/”,補充這個”/”的操作是客戶端自己完成的,詳見rfc7230 5.3.1節;
c、uri只支援很少的一部分字元(見rfc3986 1.2.1節,不是ASCII字符集,因為白空格SP也是uri不支援的,需要轉義成”%20”或者變成”+”),不支援的字元需要進行相應轉換才是合法uri,常見轉換方式有URLEncode和用於URL的Base64編碼(編碼後的字串沒有”+”和”/”的那種)。順便說下,有些瀏覽器(chrome/firefox)的位址列看著可以顯示中文,但這只是視覺效果,你copy一份就知道了;
d、http uri傳遞陣列,有人說用英文逗號分隔a=1,2,有人說用a=1&a=2這種形式,有人說a[]=1&a[]=2這種形式,實際上服務端最初接收到的都是Map(String -> String),具體能不能轉化成陣列還得看你使用的web框架支不支援。
4、HTTP version
目前最常見的是HTTP/1.1,其他的還有HTTP/0.9、HTTP/1.0、HTTP/1.2、HTTP/2.0,2.0是有重大改進的新版本。如果伺服器不支援某個HTTP版本,一般會返回”501 Not Implemented”。
跟HTTP Method一樣,Version欄位在很多實現中也是區分大小寫的,碰見錯誤的大小寫,tomcat7會返回”505 HTTP Version Not Supported”,vertx返回”501 Not Implemented”。
5、HTTP Request Headers
HTTP請求頭相當於HTTP方法的控制引數,主要是輔助http方法實現某些功能。http請求頭是String: String的鍵值對,在HTTP1.1中key是不區分大小寫的,HTTP2.0中是要求全部小寫key,value部分除非有特殊規定(比如Content-Coding等規定字符集、編碼的,具體的可以檢視rfc2616),否則是大小寫敏感。HTTP請求頭除了幾個rfc定義的幾個,還可以由程式自己定義,所以這個東西很靈活。
http不限定headers的整體長度和數目,但是伺服器預設實現一般都有限制,tomcat8的預設是maxHeaderCount=100,netty 的 HttpServerCodec預設是headerSize=4096,瀏覽器也可能會限制這些。
rfc中關於Headers的可以檢視rfc2616的第14節、rfc7231的第5節和第7節,關於req和resp的都有。
列一下常見的HTTP Request Heaaders:
Accept:客戶端能夠接收的內容型別
Accept-Charset:客戶端可以接受的字符集
Accept-Encoding:瀏覽器可以支援的web伺服器返回內容壓縮編碼型別
Accept-Language:客戶端可接受的語言
Connection:是否需要持久連線(HTTP 1.1預設此header為keep-alive)
Cookie:這個都知道,具體關於cookie可以自己再谷歌百度下,更多更詳細,這個header還是很重要的
Content-Length:request的body部分的位元組長度
Content-Type:body部分對應的MIME資訊
Host:指定請求的伺服器的域名和埠號
Referer:先前網頁的地址,即從你是從哪裡訪問到當前的靜態資源/介面,簡單點就是你從哪裡點進來的
User-Agent:User-Agent的內容包含發出請求的使用者資訊
Transfer-Encoding:有兩個可選值,預設是identity,這個也是不設定此Header時的http預設值;trunked,代表使用塊傳輸編碼,此值會無效Content-Length
Origin:cors跨域指明發起請求的js的來源
Access-Control-Request-xxxx:用於跨域請求,詢問伺服器相應的http請求屬效能夠使用哪些值,具體可以看下點上面兩個關於跨域的博文連結進去看下。
常見的HTTP Response Heaaders:
Allow:對某網路資源的有效的請求行為,不允許則返回405
Content-Encoding:對應req的Accept-Encoding
Content-Language:對應req的 Accept-Language
Content-Type:body部分對應的MIME資訊
Content-Length:resp body的長度,規則同req body,受Transfer-Encoding影響
Cache-Control:客戶端快取控制,快取在客戶端,使用chrome檢視返回碼是200,但是會標註”from disk cache/from cache”,這類快取有效是不會真的向伺服器發起請求的,詳見rfc2616 14.9節
Expires:資源過期時間,也是客戶端快取控制,優先順序比Cache-Controll低
If-xxxx:這結果比較的,主要是判斷資源是否修改,如果沒有修改,伺服器返回304 Not Modified但是不傳送body部分有修改則返回最新的資源。這是是一種服務端快取機制,可以讓伺服器不傳送body部分,可以減輕網路負載,客戶端收到304後是從自己的本地快取中取出body部分
E-Tag:一般同上面的If-xxxx配合使用,這兩個可以參考rfc2616 第14節
Location:返回碼301/302時的重定向url
Pragma:也是經常被用於快取從中,比如Pragma: no-cache
Set-Cookie:設定cookie
Refresh:告訴瀏覽器多久後重新整理一次,可以指定重新整理重定向url,常用於http錯誤頁面,這個並不是規範的header,不過大多數瀏覽器都支援
Response Header 與 HTML <meta http-equiv=”xxx”> 的關係:
在早期很多http伺服器實現得很簡單,有些伺服器不支援設定header,這就造成了問題。一個明顯的就是伺服器無法設定header中的Content-Type,也無法對靜態html頁面動態使用Content-Type,很容易出現編碼錯誤。為了解決這種情況,就讓html自帶一些資訊,這些資訊充當http resp headers,於是就出現了<meta http-equiv=”xxx”>這個標記。<meta http-equiv=”xxx”>的優先順序是低於resp headers的,它是為了在無headers的情況下充當headers,有了正宗的headers它肯定要讓路。
參考:http://reference.sitepoint.com/html/meta/http-equiv
有些Header能夠控制body部分,那些放在body部分說。
6、HTTP Request body
http req body和resp body的各種規則基本一樣,這裡放在一起說。
http req/resp body可以是任何型別的資料,不過它一般與req/resp Header中的Content-Type/Content-Length/Content-Encoding配合使用,許多web後臺框架/http客戶端會使用這三項的值來解析req/resp body中的資料。
GET請求是可以包含req body的,body部分的長度可以是無限的(http服務程式配置更改),這兩個本人前一篇博文已經說了,其他的關於方法與body的關係可以檢視rfc2616和rfc7231.
當http伺服器收到靜態的資原始檔請求時,依據該靜態資原始檔字尾名在mime配置檔案中找到對應的<extName, mimeType>對,把其中的mimeType設定給resp的Content-Type,客戶端再根據resp的Content-Type的處理靜態檔案。通常你可以把Content-Type和mimeType理解成一個東西。
下面列出一些常見的靜態檔案Content-Type(mimeType):
text/plain:純文字,在一些瀏覽器中text/plain,也被用來相容json或者xml
text/css:css檔案
text/html:html檔案
application/javascript:js檔案
application/json:標準的json
application/xml:標準的xml
上面幾項文字格式的Content-Type可以同時指定字元編碼,比如text/html; charset=utf-8
image/gif、image/jpeg、image/png:常見的圖片
application/octet-stream:二進位制流
application/msword:doc檔案
更多的可以參考tomcat的web.xml或者nginx的mime配置檔案。
再說兩個比較重要的關於req的Content-Type:
application/x-www-form-urlencoded:form表單預設的Content-Type,資料被編碼為鍵值對,用”&”分隔,跟URL傳參的格式一樣;
multipart/form-data:最常見的web http檔案上傳的Content-Type值。
這兩個重要是因為許多web框架碰到這兩個才會自動解析表單資料,不設定又沒有手動解析的話就相當於沒有post表單資料。
對於服務端呼叫http,Content-Type不是那麼重要,服務端的設計一般都知道如何解析body資料。
Content-Length、Transfer-Encoding與body的關係:
如果沒有設定Transfer-Encoding: chunked,那麼就Content-Length的值就是body的長度,這需要在http中一次性發送這個body的所有內容。
如果設定了Transfer-Encoding: chunked,那麼代表使用塊傳輸編碼,也就是body部分可以分塊傳輸,不用一次性發送這個body的所有內容,對方會按照chunked格式分批讀取資料並進行相應處理,此項在傳輸大內容時能夠減輕服務端/客戶端的壓力。使用塊傳輸編碼,預設會告訴對方,body傳輸長度未知,這時Content-Length是會被無視的。
塊傳輸編碼現在常用於服務端的response,客戶端的req很少使用這個。因為客戶端在http通訊中佔有主動性,可以很輕鬆的發起多次http請求來進行分塊,瀏覽器也支援各種外掛來實現功能豐富的大檔案的上傳;對於一次http請求,服務端是無法多次有效地響應的,必須得在一次http resp中響應完成,要不就會丟失狀態資訊,響應失敗。
如果一個req/resp沒有設定Transfer-Encoding: chunked,而且也沒有指定Content-Length,或者Content-Length的值並不是body的長度,那麼這個req/resp通常會被認為是無效的、錯誤的,伺服器一般是返回“400 Bad Request”。正因為如此,常見的http服務程式和客戶程式都會在發起req/resp前主動計算並設定Content-Length這個值,或者使用chunked編碼傳輸,而不是由人為手動指定。
Transfer-Encoding的預設值是identity,也就是預設情況是使用Content-Length。
更多的關於Transfer-Encoding: chunked的知識可以檢視rfc2616的3.6節等。
Content-Encoding:body的內容編碼,一般是壓縮編碼而非字元編碼,字元編碼在Content-Type中指定,預設是identity但是不被允許設定為此值(對應的Accept-Encoding則可以設定此值),這項配合Transfer-Encoding: chunked更好,能較好的實現流式壓縮併發送,不用等全部資料都完成了再壓縮、傳送。
三、HTTP Response
1、Http Response 基本格式
2、Response Http Version
此項具體返回什麼Http版本是由http服務程式實現的,狀態碼常見的有”501 Not Implemented”或者”505 HTTP Version Not Supported”。
3、Stats-Code和Description
這個大家應該很熟悉,它就是一個三位數字,description與stats-code配合使用用於描述狀態碼。詳細的可以看rfc2616第10節和rfc7230第6節,這裡列些常用的。
1xx:臨時resp的狀態碼,此狀態碼的resp不能包含body。此狀態是過渡狀態,平時正常使用根本碰不到,程式設計中使用封裝好的http客戶端也基本不會遇到這個,但在一些tcp框架中,比如netty,其中的HttpObjectAggregator中就有1xx的處理,自己用netty處理http有時候也要考慮這種狀態碼。
2xx:表示請求最終成功的狀態碼,這個最常見,一般是200 OK 和 204 No Content,204是不能有resp body的,這個狀態碼告訴瀏覽器,請求成功了,但是沒有內容給你,你可以不用重新整理頁面。
3xx:表示需要進一步操作,經常用於重定向,常見的301 Moved Permanently 永久改變導致重定向,302 Found 臨時改變導致中定向,304 Not Modified 資源無修改請從你自己的本地快取總取。301和302在重定向上功能是基本沒區別的,在爬蟲這種收錄url的程式中有區別,碰見301一般爬蟲會把url替換成重定向後新的url,碰見302則不會進行這種替換,因此302可以保留之前被搜尋引擎收錄的地址,所以實際中還是302更好更常見。
4xx:請求方有錯誤,常見的400 Bad Request 語法錯誤,403 Forbidden 請求被禁止(通常是許可權問題導致的,svn中常見這個),404 Not Found 這個大家都知道,405 Method Not Allowed 請求中使用了錯誤的http method(此狀態碼要返回Allow這種header資訊告訴客戶端哪些方法是可行的)。
5xx:應答方有錯誤,常見的500 Internal Server Error 伺服器處理請求時出現錯誤,501 伺服器未實現某些方法(跟405有些區別,405是伺服器中某些資源不支援請求方法,但是伺服器本身能識別處理這種方法,其他的資源可以用這種方法;501是伺服器本身就不識別處理請求方法,對所有資源使用這種方法都是501),502/504 nginx代理在碰到上游resp無效/resp超時時的返回,直接訪問http伺服器一般碰不到這兩種狀態碼。
4、Response Header
見上面的Request Header
5、Response body
見上面的 Request Body
以上內容,如有問題,煩請指出,謝謝!