WEB请求处理六:浏览器HTTP协议漫谈
為什么80%的碼農都做不了架構師?>>> ??
#0 系列目錄#
- WEB請求處理
- WEB請求處理一:瀏覽器請求發起處理
- WEB請求處理二:Nginx請求反向代理
- WEB請求處理三:Servlet容器請求處理
- WEB請求處理四:Tomcat配置實踐
- WEB請求處理五:MVC框架請求處理
#1 HTTP報文# HTTP報文是面向文本的,報文中的每一個字段都是一些ASCII碼串,各個字段的長度是不確定的。HTTP有兩類報文:請求報文和響應報文。 ##1.1 HTTP請求報文解剖## ###1.1.1 請求報文結構### HTTP請求報文由3部分組成(請求行+請求頭+請求體):
下面是一個實際的請求報文:
①為請求方法,GET和POST是最常見的HTTP方法,除此以外還包括DELETE、HEAD、OPTIONS、PUT、TRACE。不過,當前的大多數瀏覽器只支持GET和POST,Spring 3.0提供了一個HiddenHttpMethodFilter,允許你通過“_method”的表單參數指定這些特殊的HTTP方法(實際上還是通過POST提交表單)。服務端配置了HiddenHttpMethodFilter后,Spring會根據_method參數指定的值模擬出相應的HTTP方法,這樣,就可以使用這些HTTP方法對處理方法進行映射了。?
GET:最常見的一種請求方式,服務器將URL定位的資源放在響應報文的數據部分,回送給客戶端。地址中”?”之后的部分就是通過GET發送的請求數據,各個數據之間用”&”符號隔開。顯然,這種方式不適合傳送私密數據。另外,由于不同的瀏覽器對地址的字符限制也有所不同,一般最多只能識別1024個字符,所以如果需要傳送大量數據的時候,也不適合使用GET方式。
POST:對于上面提到的不適合使用GET方式的情況,可以考慮使用POST方式,因為使用POST方法可以允許客戶端給服務器提供信息較多。POST方法將請求參數封裝在HTTP請求數據中,以名稱/值的形式出現,可以傳輸大量數據,這樣POST方式對傳送的數據大小沒有限制,而且也不會顯示在URL中。
關于HTTP請求GET和POST的區別:
②為請求對應的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/plainAccept屬性的值可以為一個或多個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響應報文:?①報文協議及版本;?②狀態碼及狀態描述;?③響應報文頭,也是由多個屬性組成;?④響應報文體,即我們真正要的“干貨”;###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是基于傳輸層TCP協議的**,而TCP是一個端到端的面向連接的協議。**所謂的端到端可以理解為進程到進程之間的通信**。所以HTTP在開始傳輸之前,首先需要建立TCP連接,而TCP連接的過程需要所謂的“三次握手”。概念如圖所示。在TCP三次握手之后,建立了TCP連接,此時HTTP就可以進行傳輸了。一個重要的概念是面向連接,**即HTTP在傳輸完成之前并不斷開TCP連接**。在HTTP1.1中(通過Connection頭設置)這是默認行為。所謂的HTTP傳輸完成,我們通過一個具體的例子來看。比如訪問我的博客,使用Fiddler來截取對應的請求和響應。如圖所示:可以看出,雖然僅僅訪問了我的博客,但所獲取的不僅僅是一個HTML,而是瀏覽器對HTML解析的過程中,如果發現需要獲取的內容,會再次發起HTTP請求去服務器獲取,比如上圖中的那個common2.css。**這上面19個HTTP請求,只依靠一個TCP連接就夠了,這就是所謂的持久連接。**也是所謂的一次HTTP請求完成。#3 瀏覽器解析html代碼,并請求html代碼中的資源# 瀏覽器拿到index.html文件后,就開始解析其中的html代碼,遇到js/css/image等靜態資源時,就向服務器端去請求下載(會使用多線程下載,每個瀏覽器的線程數不一樣),**這個時候就用上keep-alive特性了,建立一次HTTP連接,可以請求多個資源,下載資源的順序就是按照代碼里的順序**,但是由于每個資源大小不一樣,而瀏覽器又多線程請求請求資源,所以從下圖看出,這里顯示的順序并不一定是代碼里面的順序。瀏覽器在請求靜態資源時(在未過期的情況下),向服務器端發起一個http請求(詢問自從上一次修改時間到現在有沒有對資源進行修改),如果服務器端返回304狀態碼(告訴瀏覽器服務器端沒有修改),那么瀏覽器會直接讀取本地的該資源的緩存文件。**瀏覽器具體渲染頁面,內部工作原理,請參考:[《前端必讀:瀏覽器內部工作原理》](http://kb.cnblogs.com/page/129756/)**。轉載于:https://my.oschina.net/xianggao/blog/715041
總結
以上是生活随笔為你收集整理的WEB请求处理六:浏览器HTTP协议漫谈的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Bootstrap~大叔封装的弹层
- 下一篇: 2016网络安全***赛记录