#0 系列目錄#css
#1 HTTP報文# HTTP報文是面向文本的,報文中的每個字段都是一些ASCII碼串,各個字段的長度是不肯定的。HTTP有兩類報文:請求報文和響應報文。 ##1.1 HTTP請求報文解剖## ###1.1.1 請求報文結構### HTTP請求報文由3部分組成(請求行+請求頭+請求體):html
下面是一個實際的請求報文:前端
①爲請求方法,GET和POST是最多見的HTTP方法,除此之外還包括DELETE、HEAD、OPTIONS、PUT、TRACE。不過,當前的大多數瀏覽器只支持GET和POST,Spring 3.0提供了一個HiddenHttpMethodFilter,容許你經過「_method」的表單參數指定這些特殊的HTTP方法(實際上仍是經過POST提交表單)。服務端配置了HiddenHttpMethodFilter後,Spring會根據_method參數指定的值模擬出相應的HTTP方法,這樣,就可使用這些HTTP方法對處理方法進行映射了。 java
GET:最多見的一種請求方式,服務器將URL定位的資源放在響應報文的數據部分,回送給客戶端。地址中」?」以後的部分就是經過GET發送的請求數據,各個數據之間用」&」符號隔開。顯然,這種方式不適合傳送私密數據。另外,因爲不一樣的瀏覽器對地址的字符限制也有所不一樣,通常最多隻能識別1024個字符,因此若是須要傳送大量數據的時候,也不適合使用GET方式。web
POST:對於上面提到的不適合使用GET方式的狀況,能夠考慮使用POST方式,由於使用POST方法能夠容許客戶端給服務器提供信息較多。POST方法將請求參數封裝在HTTP請求數據中,以名稱/值的形式出現,能夠傳輸大量數據,這樣POST方式對傳送的數據大小沒有限制,並且也不會顯示在URL中。spring
關於HTTP請求GET和POST的區別:瀏覽器
- GET提交:請求的數據會附在URL以後(就是把數據放置在HTTP協議頭<request-line>中),以?分割URL和傳輸數據,多個參數用&鏈接。若是數據是英文字母/數字,原樣發送,若是是空格,轉換爲+,若是是中文/其餘字符,則直接把字符串用BASE64加密,得出如: %E4%BD%A0%E5%A5%BD,其中%XX中的XX爲該符號以16進製表示的ASCII。 POST提交:把提交的數據放置在是HTTP的報文體<request-body>中。 所以,GET提交的數據會在地址欄中顯示出來,而POST提交,地址欄不會改變。
- 傳輸數據的大小: 首先聲明,HTTP協議沒有對傳輸的數據大小進行限制,HTTP協議規範也沒有對URL長度進行限制。 而在實際開發中存在的限制主要有: GET:特定瀏覽器和服務器對URL長度有限制,例如IE對URL長度的限制是2083字節(2K+35)。對於其餘瀏覽器,如Netscape、FireFox等,理論上沒有長度限制,其限制取決於操做系統的支持。 所以對於GET提交時,傳輸數據就會受到URL長度的限制。 POST:因爲不是經過URL傳值,理論上數據不受限。但實際各個WEB服務器會規定對post提交數據大小進行限制,Apache、IIS6都有各自的配置。 3. 安全性: POST的安全性要比GET的安全性高。注意:這裏所說的安全性和上面GET提到的「安全」不是同個概念。上面「安全」的含義僅僅是不做數據修改,而這裏安全的含義是真正的Security的含義,好比:經過GET提交數據,用戶名和密碼將明文出如今URL上,由於(1)登陸頁面有可能被瀏覽器緩存,(2)其餘人查看瀏覽器的歷史紀錄,那麼別人就能夠拿到你的帳號和密碼了,
②爲請求對應的URL地址,它和報文頭的Host屬性組成完整的請求URL。緩存
③爲協議名稱及版本號。安全
④爲HTTP的報文頭,報文頭包含若干個屬性,格式爲「屬性名:屬性值」,服務端據此獲取客戶端的信息。服務器
⑤爲報文體,它將一個頁面表單中的組件值經過param1=value1¶m2=value2
的鍵值對形式編碼成一個格式化串,它承載多個請求參數的數據。不但報文體能夠傳遞請求參數,請求URL也能夠經過相似於「/chapter15/user.html?param1=value1¶m2=value2
」的方式傳遞請求參數。
**對照上面的請求報文,咱們把它進一步分解,你能夠看到一幅更詳細的結構圖: **
###1.1.2 HTTP請求報文頭屬性### 報文頭屬性是什麼東西呢?咱們不妨以一個小故事來講明吧。
快到中午了,張三丰不想去食堂吃飯,因而打電話叫外賣:老闆,我要一份[魚香肉絲],要12:30以前給我送過來哦,我在江湖湖公司研發部,叫張三丰。
這裏,你要[魚香肉絲]至關於HTTP報文體,而「12:30以前送過來」,你叫「張三丰」等信息就至關於HTTP的報文頭。它們是一些附屬信息,幫忙你和飯店老闆順利完成此次交易。
請求HTTP報文和響應HTTP報文都擁有若干個報文關屬性,它們是爲協助客戶端及服務端交易的一些附屬信息。
請求報文可經過一個「Accept」報文頭屬性告訴服務端 客戶端接受什麼類型的響應。
以下報文頭至關於告訴服務端,俺客戶端可以接受的響應類型僅爲純文本數據啊,你丫別發其它什麼圖片啊,視頻啊過來,那樣我會歇菜的~~~:
Accept:text/plain
Accept屬性的值能夠爲一個或多個MIME類型的值,關於MIME類型,你們請參考:http://en.wikipedia.org/wiki/MIME_type
客戶端的Cookie就是經過這個報文頭屬性傳給服務端的哦!以下所示:
Cookie: $Version=1; Skin=new;jsessionid=5F4771183629C9834F8382E23BE13C4C
服務端是怎麼知道客戶端的多個請求是隸屬於一個Session呢?注意到後臺的那個jsessionid=5F4771183629C9834F8382E23BE13C4C木有?原來就是經過HTTP請求報文頭的Cookie屬性的jsessionid的值關聯起來的!(固然也能夠經過重寫URL的方式將會話ID附帶在每一個URL的後面哦)。
表示這個請求是從哪一個URL過來的,假如你經過google搜索出一個商家的廣告頁面,你對這個廣告頁面感興趣,鼠標一點發送一個請求報文到商家的網站,這個請求報文的Referer報文頭屬性值就是http://www.google.com。
唐僧到了西天. 如來問:儂是否是從東土大唐來啊? 唐僧:厲害!你咋知道的! 如來:呵呵,我偷看了你的Referer...
不少貌似神奇的網頁監控軟件(如著名的 我要啦),只要在你的網頁上放上一段JavaScript,就能夠幫你監控流量,全國訪問客戶的分佈狀況等報表和圖表,其原理就是經過這個Referer及其它一些HTTP報文頭工做的。
對緩存進行控制,如一個請求但願響應返回的內容在客戶端要被緩存一年,或不但願被緩存就能夠經過這個報文頭達到目的。
如如下設置,至關於讓服務端將對應請求返回的響應內容不要在客戶端緩存:
Cache-Control: no-cache ``` 5. **User-Agent:** 產生請求的瀏覽器類型。 6. **Host:** 請求的主機名,容許多個域名同處一個IP地址,即虛擬主機。 ###1.1.3 如何訪問請求報文頭### 因爲請求報文頭是客戶端發過來的,服務端固然只能讀取了,如下是HttpServletRequest一些用於讀取請求報文頭的API:
// 獲取請求報文中的屬性名稱 java.util.Enumeration<java.lang.String> getHeaderNames(); // 獲取指定名稱的報文頭屬性的值 java.lang.String getHeader(java.lang.String name);
因爲一些請求報文頭屬性「太著名」了,所以HttpServletRequest爲它們提供了VIP的API:
// 獲取報文頭中的Cookie(讀取Cookie的報文頭屬性) Cookie[] getCookies() ; // 獲取客戶端本地化信息(讀取 Accept-Language 的報文頭屬性) java.util.Locale getLocale() // 獲取請求報文體的長度(讀取Content-Length的報文頭屬性) int getContentLength();
// 獲取請求所關聯的HttpSession,其內部的機理是經過讀取請求報文頭中Cookie屬性的JSESSIONID的值, // 在服務端的一個會話Map中,根據這個JSESSIONID獲取對應的HttpSession的對象 HttpSession getSession()
##1.2 HTTP響應報文解剖## ###1.2.1 響應報文結構### HTTP的響應報文也由三部分組成(**響應行+響應頭+響應體**): ![HTTP響應報文結構](http://upload-images.jianshu.io/upload_images/2062729-1a7bd8708dbc9390.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 如下是一個實際的HTTP響應報文: ![實際的HTTP響應報文](http://upload-images.jianshu.io/upload_images/2062729-b0d87f06a8ae130a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ①報文協議及版本; ②狀態碼及狀態描述; ③響應報文頭,也是由多個屬性組成; ④響應報文體,即咱們真正要的「乾貨」; ###1.2.2 響應狀態碼### 和請求報文相比,響應報文多了一個「響應狀態碼」,它以「清晰明確」的語言告訴客戶端本次請求的處理結果。 **HTTP的響應狀態碼由5段組成:** > 1xx 消息,**通常是告訴客戶端,請求已經收到了,正在處理**,別急...。 > > 2xx **處理成功**,通常表示:請求收悉、我明白你要的、請求已受理、已經處理完成等信息。 > > 3xx **重定向到其它地方**。它讓客戶端再發起一個請求以完成整個處理。 > > 4xx **處理髮生錯誤,責任在客戶端**,如客戶端的請求一個不存在的資源,客戶端未被受權,禁止訪問等。 > > 5xx **處理髮生錯誤,責任在服務端**,如服務端拋出異常,路由出錯,HTTP版本不支持等。 **如下是幾個常見的狀態碼:** > **200 OK** 你最但願看到的,即處理成功! > > **301 永久重定向** Location響應首部的值仍爲當前URL,所以爲隱藏重定向; > > **302 臨時重定向** 顯式重定向, Location響應首部的值爲新的URL。 > > **303 See Other** redirect到其它的頁面,目標的URL經過響應報文頭的Location告訴你。 > > **304 Not Modified** 告訴客戶端,你請求的這個資源至你上次取得後,並無更改,你直接用你本地的緩存吧,我很忙哦,你能不能少來煩我啊! > > **400 Bad Request** 客戶端請求有語法錯誤,不能被服務器所理解。 > > **401 Unauthorized** 請求未經受權,這個狀態代碼必須和WWW-Authenticate報頭域一塊兒使用。 > > **403 Forbidden** 服務器收到請求,可是拒絕提供服務。 > > **404 Not Found** 你最不但願看到的,即找不到頁面。如你在google上找到一個頁面,點擊這個連接返回404,表示這個頁面已經被網站刪除了,google那邊的記錄只是美好的回憶。 > > **500 Internal Server Error** 看到這個錯誤,你就應該查查服務端的日誌了,確定拋出了一堆異常,別睡了,起來改BUG去吧! > > **503 Server Unavailable** 服務器當前不能處理客戶端的請求,一段時間後可能恢復正常,舉個例子:HTTP/1.1 200 OK(CRLF)。 其它的狀態碼參見:[http://en.wikipedia.org/wiki/List_of_HTTP_status_codes](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes) 有些響應碼,Web應用服務器會自動給生成。你能夠經過HttpServletResponse的API設置狀態碼:
// 設置狀態碼,狀態碼在HttpServletResponse中經過一系列的常量預約義了,如SC_ACCEPTED,SC_OK void setStatus(int sc)
###1.2.3 HTTP響應報文頭屬性### **Cache-Control:**響應輸出到客戶端後,服務端經過該報文頭屬告訴客戶端如何控制響應內容的緩存。 下面的設置讓客戶端對響應內容緩存3600秒,也即在3600秒內,若是客戶再次訪問該資源,直接從客戶端的緩存中返回內容給客戶,不要再從服務端獲取(固然,這個功能是靠客戶端實現的,服務端只是經過這個屬性提示客戶端「應該這麼作」,作不作,仍是決定於客戶端,若是是本身宣稱支持HTTP的客戶端,則就應該這樣實現)。
Cache-Control: max-age=3600
**ETag:**一個表明響應服務端資源(如頁面)版本的報文頭屬性,若是某個服務端資源發生變化了,這個ETag就會相應發生變化。它是Cache-Control的有益補充,可讓客戶端「更智能」地處理何時要從服務端取資源,何時能夠直接從緩存中返回響應。 關於ETag的說明,你能夠參見:[http://en.wikipedia.org/wiki/HTTP_ETag](http://en.wikipedia.org/wiki/HTTP_ETag)。 Spring 3.0還專門爲此提供了一個`org.springframework.web.filter.ShallowEtagHeaderFilter`(實現原理很簡單,對JSP輸出的內容MD5,這樣內容有變化ETag就相應變化了),用於生成響應的ETag,**由於這東東確實能夠幫助減小請求和響應的交互**。 下面是一個ETag:
ETag: "737060cd8c284d8af7ad3082f209582d"
**Location:**在JSP中讓頁面Redirect到一個某個A頁面中,實際上是讓客戶端再發一個請求到A頁面,這個須要Redirect到的A頁面的URL,其實就是經過響應報文頭的Location屬性告知客戶端的,以下的報文頭屬性,將使客戶端redirect到iteye的首頁中。
Location: http://www.iteye.com
**Set-Cookie:**服務端能夠設置客戶端的Cookie,其原理就是經過這個響應報文頭屬性實現的。
Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1
Connection 使用keep-alive特性; Content-Encoding 使用gzip方式對資源壓縮; Content-type MIME類型爲html類型,字符集是 UTF-8; Date 響應的日期; Server 使用的WEB服務器; Transfer-Encoding:chunked 分塊傳輸編碼 是http中的一種數據傳輸機制,容許HTTP由網頁服務器發送給客戶端應用(一般是網頁瀏覽器)的數據能夠分紅多個部分,分塊傳輸編碼只在HTTP協議1.1版本(HTTP/1.1)中提供;
更多其它的HTTP響應頭報文,參見:[http://en.wikipedia.org/wiki/List_of_HTTP_header_fields](http://en.wikipedia.org/wiki/List_of_HTTP_header_fields) ###1.2.4 如何寫HTTP請求報文頭### 在服務端能夠經過HttpServletResponse的API寫響應報文頭的屬性:
// 添加一個響應報文頭屬性 void setHeader(String name, String value)
像Cookie,Location這些響應都是有福之人,HttpServletResponse爲它們都提供了VIP版的API:
// 添加Cookie報文頭屬性 void addCookie(Cookie cookie) // 不但會設置Location的響應報文頭,還會生成303的狀態碼呢,二者天仙配呢 void sendRedirect(String location)
#2 HTTP傳輸處理# **在一個網絡中。傳輸數據須要面臨三個問題:** > 1. 客戶端**如何知道所請求內容的位置**? > 2. 當客戶端知道所請求內容的位置後,**如何獲取所請求的內容**? > 3. 所請求內容以**何種形式組織以便被客戶端所識別**? 對於WEB來講,回答上面三種問題分別採用三種不一樣的技術,分別爲:**統一資源定位符(URI),超文本傳輸協議(HTTP)和超文本標記語言(HTML)**。對於大多數WEB開發人員來講URI和HTML都是很是的熟悉。而HTTP協議在不少WEB技術中都被封裝的過多使得HTTP反而最不被熟悉。 HTTP做爲一種傳輸協議,也是像HTML同樣隨着時間不斷演進的,目前流行的HTTP1.1是HTTP協議的第三個版本。 在Internet中全部的傳輸都是經過TCP/IP進行的。**HTTP協議做爲TCP/IP模型中應用層的協議也不例外**。HTTP在網絡中的層次如圖所示: ![HTTP網絡層次圖](http://upload-images.jianshu.io/upload_images/2062729-646ca06db173fc49.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 能夠看出,**HTTP是基於傳輸層TCP協議的**,而TCP是一個端到端的面向鏈接的協議。**所謂的端到端能夠理解爲進程到進程之間的通訊**。因此HTTP在開始傳輸以前,首先須要創建TCP鏈接,而TCP鏈接的過程須要所謂的「三次握手」。概念如圖所示。 ![TCP鏈接三次握手](http://upload-images.jianshu.io/upload_images/2062729-bdc0c6fe5d1d9d2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 在TCP三次握手以後,創建了TCP鏈接,此時HTTP就能夠進行傳輸了。一個重要的概念是面向鏈接,**即HTTP在傳輸完成以前並不斷開TCP鏈接**。在HTTP1.1中(經過Connection頭設置)這是默認行爲。所謂的HTTP傳輸完成,咱們經過一個具體的例子來看。 好比訪問個人博客,使用Fiddler來截取對應的請求和響應。如圖所示: ![使用Fiddler來截取對應的請求和響應](http://upload-images.jianshu.io/upload_images/2062729-b7d0a109bd2f755d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 能夠看出,雖然僅僅訪問了個人博客,但所獲取的不只僅是一個HTML,而是瀏覽器對HTML解析的過程當中,若是發現須要獲取的內容,會再次發起HTTP請求去服務器獲取,好比上圖中的那個common2.css。**這上面19個HTTP請求,只依靠一個TCP鏈接就夠了,這就是所謂的持久鏈接。**也是所謂的一次HTTP請求完成。 #3 瀏覽器解析html代碼,並請求html代碼中的資源# 瀏覽器拿到index.html文件後,就開始解析其中的html代碼,遇到js/css/image等靜態資源時,就向服務器端去請求下載(會使用多線程下載,每一個瀏覽器的線程數不同),**這個時候就用上keep-alive特性了,創建一次HTTP鏈接,能夠請求多個資源,下載資源的順序就是按照代碼裏的順序**,可是因爲每一個資源大小不同,而瀏覽器又多線程請求請求資源,因此從下圖看出,這裏顯示的順序並不必定是代碼裏面的順序。 瀏覽器在請求靜態資源時(在未過時的狀況下),向服務器端發起一個http請求(詢問自從上一次修改時間到如今有沒有對資源進行修改),若是服務器端返回304狀態碼(告訴瀏覽器服務器端沒有修改),那麼瀏覽器會直接讀取本地的該資源的緩存文件。 ![瀏覽器請求資源](http://upload-images.jianshu.io/upload_images/2062729-9e0b7a9153cb23bc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) **瀏覽器具體渲染頁面,內部工做原理,請參考:[《前端必讀:瀏覽器內部工做原理》](http://kb.cnblogs.com/page/129756/)**。