探求網頁同步提交、ajax和comet不為人知的秘密(中篇)

來源:互聯網
上載者:User

標籤:style   blog   http   io   color   ar   os   使用   sp   

  深入研究某項技術,瞭解使用這些技術的細節,其實最終目的都是為了完成一個選擇問題:當我們要使用這些技術解決某個具體的問題時候我們到底該如何去選擇。如果碰到有兩種技術可以讓我們達到同樣的目的,我們就會不自然的去比較它們之間的差異,通過對這些差異的梳理,我們就能得出在使用它們時候我們到底該如何取捨。

  上篇裡我講到XMLHttpRequest可以更加精確的控制http請求的報文頭,如是乎我就去尋找在同步提交裡我是否能像XMLHttpRequest那樣的去控制http的頭部資訊呢,最終我發現同步提交下我們可以在html的head裡設定meta標籤,使用http-equiv來設定http的頭部資訊,不過meta的頭部資訊其實是伺服器給瀏覽器的回應標頭部資訊,而不是要求標頭部資訊,下面是我們最常用,最能影響頁面展示的meta寫法,如下所示:

<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"><meta http-equiv="expires" content="31 Dec 2099">

 

 

  這個頭部是告訴瀏覽器該頁面是一個html文檔,字元集是UTF-8,還告訴瀏覽器你要將這個頁面緩衝在瀏覽器裡,如果使用者不清理瀏覽器緩衝,那麼該頁面在2099年12月31號前都可以從緩衝讀取,不需要在從伺服器下載整個html文檔了。

  Expire是服務端指定瀏覽器的緩衝行為,在web前端最佳化裡它所代表的緩衝能力是非常重要,ajax也能使用瀏覽器緩衝,不過這塊知識需要和我後面要講的內容相關,所以這裡先暫時擱置一下。

  Content-type在上篇文章裡多次出現,我們開發頁面時候,html的head裡該屬性可謂是一個熟悉的陌生人,在很多用來開發頁面的IDE裡,我們選擇模板建立一個頁面,這個標籤都是預設帶有的,都不用我們自己添加它,但是我在多年開發經驗我還真的碰到過content-type使用不當所帶來的技術問題。

  很早前有位朋友請教我一個這樣的問題,他們公司有個web應用,這個web應用沒有太多的頁面,大部分是背景程式,該系統的目的是對他們公司使用者提供一些特別的服務,使用者如果要使用該服務只需要在自己網站頁面裡跨域請求他們系統提供的網頁,為了保證系統的安全,這位朋友的系統會對使用者發送的請求做很多安全性的檢查,這個檢查比較耗時,但是又非常重要,因此他們為了給終端使用者以友好的使用者體驗,就在安全檢查正在處理時提供一個等待頁面,也許是因為這個朋友開發的系統服務端過重而且頁面太少太簡單,不是特別擅長頁面開發的他們開發時候就沒有單獨寫個等待頁面,而是將等待頁面放在背景servlet裡直接構造(其實就是在後台直接拼字頁面的字串了),但是他們構造時候忘了在這個簡單頁面裡加上content-type屬性,可惡的是這位朋友的系統測試時候沒有發現任何問題,可怕的是上了生產環境後某些調用他們系統的使用者在ie瀏覽器下頁面直接顯示了是原始碼而不是頁面,在非ie瀏覽器下卻是正常。不是所有人有問題,有問題也是部分瀏覽器,在自己的測試環境又類比不出問題,這怎麼搞,那位朋友的項目組花了兩天都沒弄明白怎麼回事,而這時使用者的投訴是越來越多,如是他問我碰到過這種情況嗎?當時我用ie和非ie抓包工具對比了在不同瀏覽器裡http請求的要求標頭和回應標頭,發現了區別:有的瀏覽器content-type是text/html,有的是text/plain,ie瀏覽器下都是text/plain,非ie兩種皆有,不過ie下如果是text/plain,那麼頁面就會顯示源碼,如是我把我看到的現象告訴了這位朋友,這位朋友查閱下代碼發現他們構造等待頁面時候沒有指定content-type頭,如是他們強制指定該頭為text/html,問題就解決。雖然問題解決了,但是我們還是沒有找到什麼地方出問題了,為什麼測試環境下不能重現問題的發生,這個現象對於做過互連網開發的朋友可能就很好理解了,互連網的應用部署的網路環境是非常複雜的,使用者從瀏覽器發送一個http請求到服務端,這個過程並不是像我們平時開發那樣直接到像tomcat,Jboss這些web容器,而是會經過好多重路徑,最常見的有cdn,負載平衡裝置,靜態資源伺服器例如apache等等,請求通過的這些環節都有許可權和能力更改http報文,如果你們的網站規模越大,那麼網路環境會更加複雜,幾乎很難找到一個知道這所有細節的人,所以在互連網公司都會有一個預發布環境,這個環境會盡最大努力類比真實環境,雖然這個環境也是為了測試,但很多公司的預發布環境是可以在外網訪問,如果預發布環境設計的好,就能很有效保證系統投產後的安全性,不過最有效能避免上面問題發生的手段還是自己技術、經驗要過硬。

  Content-type很有用,多理解下它可以為我們解決更多疑惑,詳細講解這個屬性不是本文的主題,下面我給感興趣的朋友兩個連結,有興趣可以瞧瞧:

http://baike.baidu.com/view/1547292.htm?fr=aladdin

http://tool.oschina.net/commons

  大多數時候,在同步請求裡我們發送http請求給服務端是不需要瞭解太多content-type的作用,其實很多http頭也不用過多關心,但是有個東西特別是像我們這種使用漢字而非英語的國家,對字元集的編碼還是要倍加註意的。

  雖然html頭部設定charset的編碼是UTF-8,這是回應標頭的字元集,但是如果我們以這個響應的頁面接著做http請求,那麼這個編碼級會影響到提交請求的字元編碼即我們如果不做特別指定那麼之後在這個頁面所有請求都是按照該編碼級進行的。那麼問題來了,如果我們想從一個UTF-8頁面請求一個後台只能接收GBK編碼資料的介面時候,那麼我們怎麼更改這個請求的編碼了,問題更進一步點,我們該如何在同步請求裡控制請求的http頭,至少是很重要的content-type屬性呢?

  同步提交核心是form,這和ajax的核心是XMLhttpRequest一樣,在form標籤下有個屬性accept-charset,該屬性規定伺服器處理表單資料所接受的字元集。accept-charset 屬性允許您指定一系列字元集,伺服器必須支援這些字元集,從而得以正確解釋表單中的資料。

  該屬性的值是用引號包含字元集名稱列表。如果可接受字元集與使用者所使用的字元即不相匹配的話,瀏覽器可以選擇忽略表單或是將該表單區別對待。此屬性的預設值是 "unknown",表示表單的字元集與包含表單的文檔的字元集相同。不過這個屬性在ie下效果並不好,甚至有時會無效,所以如果排除開發人員將編碼寫錯的原因,而是請求方和回應程式約定做這樣的轉化,對於字元集的轉化在服務端做是最好的。

         Form表單還有一個重要的屬性對http請求有很大的影響那就是enctype,它包含三個取值,如下所示:

值

描述

application/x-www-form-urlencoded

在發送前編碼所有字元(預設)

multipart/form-data

不對字元編碼。

在使用包含檔案上傳控制項的表單時,必須使用該值。

text/plain

空格轉換為 "+" 加號,但不對特殊字元編碼。

  第一行的值在我本文上篇裡我曾提到過多次,現在我們知道它在瀏覽器裡的作用了,form表單enctype預設的屬性值,它讓瀏覽器在請求發送前編碼請求的資料,為什麼瀏覽器要編碼請求資料了?根本原因是因為電腦是美國人發明的,美國人使用英文字母給電腦輸入資料,而像我們中國人則是使用方塊字漢字,而漢字對於電腦而言是一種特殊字元,不能使用幾個字母組合簡單表示的,放眼全球,這種問題不僅僅發生在漢字上,韓文,日本等等非字母的語言都會存在同樣問題,但是網路傳輸的資料都是二進位,為了讓請求資料在服務端能被正確還原服務端就必須有一個能容納世界上所有語言的字元集例如UTF-8或則特有的字元集GBK規範進行轉義,這就是application/x-www-form-urlencoded的作用,他告訴瀏覽器你要對請求資料轉碼。Form表單還能傳輸檔案,檔案是需要特別的解析器來解析,例片要圖片解析器,word文檔要office解析,但是這些檔案本身儲存就是二進位,而且是特有的二進位,因此網路傳輸時候最好能保持原樣,如是乎有了multipart/form-data屬性,有時我們不想轉碼了那麼就可以使用text/plain,這些參數最後都會體現在http要求標頭部的content-type屬性裡。

      由此可見form表單並不那麼簡單,它也幫我們開發屏蔽了許多技術細節,所以當我們不使用form表單提交同步請求時候就有可能掉進這些細節陷阱裡,最常見的一個問題就是使用非form提交的get請求,該get請求會傳參至服務端,而這個參數裡包含中文例如:websit=部落格園,如果我們直接這麼寫,瀏覽器不會給我報錯,但是到了服務端後我們會發現參數變成亂碼,url造成的亂碼是讓很多初學者頭疼的問題,不過javascript早就預料到了,如是它提供了三對可以對字串編碼的函數,分別是: escape,encodeURI,encodeURIComponent,相應3個解碼函數:unescape,decodeURI,decodeURIComponent 。這個問題的根本就是我們提交請求失去了form的保護,我們必須手動指定form裡enctype所提供的功能。關於上面三對函數,本文不做過多講述,想瞭解的朋友可以問問度娘了。

      上面內容是我講同步請求時候講掉的內容,這裡補上了,由上面的內容我們可以看出同步提交對http頭部的控制能力是很差的。

       XMLHttprequest對http頭有著強大的控制能力,它可以通過setRequestHeader(名,值)來設定http的要求標頭,方法getResponseHeader(名)擷取指定的頭,方法getReponseHeaders()擷取所有回應標頭資訊,雖然http要求標頭對於使用者是不可見的(使用者只關心http請求體的內容,因為請求才是使用者需要的),但是對於開發人員而言http要求標頭非常重要,因為http要求標頭是整個http的領導者,我們可以通過控制http要求標頭資訊達到很多傳統http請求所不能達到的目的。

       在使用XMLHttpRequest時候如果我們不對要求標頭做任何修改,預設情況下瀏覽器會發送以下http頭部資訊給伺服器,它們分別是:

Accept:瀏覽器能夠處理的內容類型;Accpet-Charset:瀏覽器能夠顯示字元集;Accept-Encoding:瀏覽器能夠處理的壓縮編碼;Accept-Language:瀏覽器當前設定的語言;Connection:瀏覽器和伺服器之間連線類型;Cookie:當前版面設定的任何Cookie;Host:發出請求頁面所在的域;Referer:發出請求的頁面的url,User-Agent:瀏覽器的使用者代理程式字串。

 

  上面九個頭部資訊是所有瀏覽器都會傳送的,不過瀏覽器類型不同,頭部資訊可能還會有點差異。

  雖然setRequestHeader方法可以修改http頭部資訊,但是如果我們修改上面9個屬性中的某些屬性會發現這種修改有時不頂用,這是某些瀏覽器出於安全考慮不讓開發人員輕易更改,好在setRequestHeader方法還能自訂要求標頭,如果我們想做一些與要求標頭相關的特別處理,最好是自己定義要求標頭。此外setRequestHeader方法使用要在open方法後,send方法前,否則會達不到效果。

         接下來是ajax技術裡另外一個重點了:回呼函數的使用。這裡有一句話:ajax的回呼函數是用來處理http請求響應的,也就是說服務端給了瀏覽器請求結果才會調用ajax的回呼函數,這個說法對嗎?我覺得很多人都會認為這句話是正確的,這裡我先不告訴大家答案,先講講關於ajax回呼函數的使用,大家請看下面的代碼:

xmlhttp.onreadystatechange=state_Change;function state_Change(){if (xmlhttp.readyState==4)  {// 4 = "loaded"  if (xmlhttp.status==200)    {// 200 = OK    // ...our code here...    }  else    {    alert("Problem retrieving XML data");    }  }}

  

  XMLHttpRequest的onreadystatechange方法用來接收回呼函數,回呼函數裡我們要判斷readyState的取值為4,status的取值為200時候,我們就認為得到了成功的響應,否則就是沒有得到成功的響應。我曾經做過一個嘗試,就是去掉xmlhttp.readyState==4代碼,我發現else裡的alert會執行多次,點完了這些對話方塊後請求任然可以正確處理,這說明了一個問題:onreadystatechange方法是和readystate值有關的,每當readystate值發生改變的時候onreadystatechange就會被執行,其實從onreadystatechange的名字就能知道這點。

  XMLHttpRequest裡readystate的取值如下所示:

    取值0:未初始化即還沒調用open方法;    取值1:啟動即已經調用了open方法,但還沒有調用send方法;    取值2:發送即調用send方法,但還沒有收到響應;    取值3:接收即已經接收到部分響應資料;    取值4:完成即接收到了全部響應資料。

 

  由此可見ajax的回呼函數並不是只有當獲得響應時候才會觸發,一個ajax請求用於記錄回呼函數的onreadystatechange方法會被調用5遍,所以我們必須使用readystate==4來表明響應成功。此外從readystate狀態值我們也可以發現onreadystatechange屬性的賦值要放在open方法之前,否則你的寫法是存在隱患的。

  瞭解了這個後,我們看看jQuery的ajax方法的使用,jQuery的ajax方法是對瀏覽器底層ajax操作的封裝,該方法屏蔽了瀏覽器實現之間的差異,同時還提供了一些方法,這些方法是對底層ajax操作的上層封裝,他能讓那些沒有深入理解原始ajax原理的人以方便,這裡我們著重看下jQuery裡ajax裡回呼函數的使用,下面是我摘抄jQuery文檔裡的內容:

如果要處理$.ajax()得到的資料,則需要使用回呼函數。beforeSend、error、dataFilter、success、complete。beforeSend 在發送請求之前調用,並且傳入一個XMLHttpRequest作為參數。error 在請求出錯時調用。傳入XMLHttpRequest對象,描述錯誤類型的字串以及一個異常對象(如果有的話)dataFilter 在請求成功之後調用。傳入返回的資料以及"dataType"參數的值。並且必須返回新的資料(可能是處理過的)傳遞給success回呼函數。success 當請求之後調用。傳入返回後的資料,以及包含成功碼的字串。complete 當請求完成之後調用這個函數,無論成功或失敗。傳入XMLHttpRequest對象,以及一個包含成功或錯誤碼的字串。

 

 

  上面的success方法就是readystate值為4,status的值為200的情形了(實際情況下status值會更多點,後面再聊這個問題),complete是當請求完成後調用的函數,這個方法其實和status值無關了,只和readystate值為4有關,beforeSend則是readystate狀態為1時候使用的,error方法就比較複雜點了,它是一個綜合錯誤的考慮,反正就是不成功就成error了,dataFilter是根據使用者定義的響應資料的類型(dataType)對返回資料做相應的轉化,一般我們很少使用該函數,都是依賴jQuery內部完成,不過這個回調是在success方法之前執行。

  dataFilter回呼函數可以對響應資料進行處理,這裡就引出了一個新問題,我們通過XMLHttpRequest擷取的響應資料有哪些類型,XMLHttpRequest有兩個屬性用來儲存響應資料,一個是responseText:文本類型的響應資料,其實就是字串,responseXML:XML文件類型的響應資料,該屬性只有在http的回應標頭是text/xml或者application/xml時候瀏覽器會幫我們轉化,如果不是上述類型,該屬性則為null。

  我們使用jQuery的ajax方法時候,通過設定dataType屬性,我們會獲得更加豐富的響應資料,下面是我摘抄的jQuery文檔的內容,如下所示:

dataType:預期伺服器返回的資料類型。如果不指定,jQuery 將自動根據 HTTP 包 MIME 資訊來智能判斷,比如XML MIME類型就被識別為XML。在1.4中,JSON就會產生一個JavaScript對象,而script則會執行這個指令碼。隨後伺服器端返回的資料會根據這個值解析後,傳遞給回呼函數。可用值:"xml": 返回 XML 文檔,可用 jQuery 處理。"html": 返回純文字 HTML 資訊;包含的script標籤會在插入dom時執行。"script": 返回純文字 JavaScript 代碼。不會自動緩衝結果。除非設定了"cache"參數。‘‘‘注意:‘‘‘在遠程請求時(不在同一個域下),所有POST請求都將轉為GET請求。(因為將使用DOM的script標籤來載入)"json": 返回 JSON 資料 。"jsonp": JSONP 格式。使用 JSONP 形式調用函數時,如 "myurl?callback=?" jQuery 將自動替換 ? 為正確的函數名,以執行回呼函數。"text": 返回純文字字串

  

  上面的大部分操作是jQuery幫我們完成的,如果我們定義了dataFilter回呼函數,該函數是在類型轉化前執行的,就是說dataFilter操作的資料是ajax返回的未經處理資料。

  當readystate值為3時候,我們會做到一些意想不到的功能,readystate為3的狀態很特別,這個時候伺服器已經給出了響應,但是響應資料並沒有全部發送給瀏覽器,不過此時你操作responseText會發現裡面是有資料的,但是這個資料不全,我曾見過使用XMLHttpRequest這個屬性有人做出了兩個效果,具體如下所述:

  效果一:雖然我們開發時候都是盡全力讓請求和回應時間變得更短,但是某些請求處理是快不起來的例如:上傳檔案或者瀏覽器接收超大的資料,如果情境是後者即使用者請求資料量很小,但是接收資料很大,接收時間很長的時候,我們一般都會做一個等待請求處理的效果,如果這個效果能有精確的進度條,那麼給使用者的體驗是非常好的,http響應報文頭裡有個屬性就是content-length,這個屬性告訴瀏覽器整個響應的大小,一般瀏覽器收到響應,就算這個響應還只有部分資料,這個回應標頭也會先發送,之所以提前,是讓伺服器知道資料接收到多少就表明接收完畢,所以在readystate為3時候我們可以通過計算收到響應的大小和content-length做比較就能知道響應接收到了那個階段了,這樣進度條的進度就會很精確。

  效果二:這個效果是我很多年前無意中看見一個國外網站發現的,可惜我現在不記得網址了,網頁的作者也是研究ajax的特點的,他通過ajax請求了一篇文章,然後這個文章會在頁面一行行的顯示出來,因為這個效果使用setTimeout函數在瀏覽器也能做,所以這位作者就控制服務端資料返回時間,瀏覽器收到部分資料後就馬上在頁面上顯示出來。

  不過上述操作有個特點,就是開發人員都會定義一個回呼函數,使用setTimeout函數輪詢這個回呼函數,setTimeout和回呼函數使用是在onreadystatechange回呼函數裡面,而且接收資料的字串要用公開變數儲存,所以這個效果做起來還是有點難度的,原因就是readystate狀態為3時候回呼函數只能調用一次。

  在jQuery的ajax方法裡有一個屬性ifModified,文檔的解釋是:

(預設: false) 僅在伺服器資料改變時擷取新資料。使用 HTTP 包 Last-Modified 頭資訊判斷。在jQuery 1.4中,他也會檢查伺服器指定的‘etag‘來確定資料沒有被修改過。

  

  這個參數說明ajax技術可以對http的緩衝進行操作,所以有些javascript的書裡講解ajax時候表示請求成功會使用兩個響應碼,一個就是常見的200,另一個是304,304的響應碼錶明該請求的響應要從瀏覽器緩衝裡取,不需要伺服器直接返回了。

  為了說清楚ajax操作緩衝,那我們首先要知道瀏覽器的緩衝機制,在web前端最佳化使用瀏覽器緩衝是一個很重要的法則,那麼如何讓瀏覽器能緩衝我們的響應呢?一般有兩種方式:

  方式一:通過expires、cache-control來控制,前者是指定緩衝到期的具體日期,後者是指定緩衝多久後到期例如10年後到期,我們第一次請求某個資源成功響應碼為200,這個時候回應標頭裡會返回一個last-modified回來,如果該資源我們設定了緩衝,那麼第二次請求在要求標頭裡會發送if-Modified-Since屬性,該屬性的值就是第一次返回的last-modified,伺服器接收後發現資源沒有改變,伺服器就會返回304響應碼回來,304響應碼就是告訴瀏覽器響應結果從瀏覽器緩衝裡取。

  方式二:使用etag,etag是另一種緩衝失效機制,使用etag的伺服器會給指定資源計算一個hash值(一般用md5算的),第一次請求時候伺服器會將這個值返回給瀏覽器,第二次請求瀏覽器會將這個值發送給伺服器,伺服器通過計算資源的hash值和傳來的值作比較後,如果值相等那麼就表明資源沒有更改,這時伺服器也會給一個304的響應碼給瀏覽器,讓瀏覽器在自己緩衝裡去找對應的響應。

  不管是那種方式,如果請求已經被緩衝,伺服器都會返回304響應碼,使用瀏覽器緩衝不代表瀏覽器不發送請求,是否取瀏覽器緩衝是伺服器來指揮的,只不過304請求不會返迴響應體,只有回應標頭,而且請求方式是get,這個請求非常小,速度非常快。

  同樣當ajax收到了304響應碼時候,ajax就會從瀏覽器緩衝裡取響應結果,這個操作XMLHttpRequest會自己幫我們做掉。

  不過關於304的情況有一點一定要注意,能被瀏覽器緩衝的請求只有get請求即原始請求一定要是get請求,post請求時沒法被瀏覽器緩衝的。

  XMLHttpRequest非常強大,我這裡的強大是想說它除了可以做非同步請求還能做同步請求,前面我講了onreadystatechange,講了readystate,其實這兩個東西應該可以說專屬於非同步請求,為什麼呢?在以前的文章裡我講到nodejs的作者選擇javascript語言原因是因為javascript的回調機制,因為有回調就可以讓單線程的javascript做出非同步效果,既然是同步提交,定義回呼函數還有意義嗎?所以如果我們使用XMLHttpquest做同步請求,我們的代碼可以這麼寫:

  xmlhttp.open("GET",url,false);  xmlhttp.send(null);  if (xmlhttp.status==200)    {// 200 = OK    // ...our code here...    }  else    {    alert("Problem retrieving XML data");    }

  

  代碼會按順序執行的,如果請求很慢,照樣阻塞頁面執行。

  看到這裡估計有人會有疑問的,ajax的同步提交有價值嗎?這還不如用form來做同步提交,這個要具體問題具體看了,我在前面講到傳統的同步提交對http的精確控制能力很差,如果某些同步請求我們需要更多的http控制那麼使用ajax比較好,此外傳統的同步請求響應要麼是覆蓋了原頁面,要麼就是用個新表單接收響應,但是同步的ajax對響應的處理靈活度更大,相比之下ajax同步請求優勢更大些。

  在jQuery的ajax裡有一個屬性就是timeout,可以為ajax請求設定逾時時間,在我前面講解ajax技術時候我們會發現XMLHttpRequest沒有一個屬性可以設定這個逾時時間屬性,其實在ie8包括ie8以上的版本裡,XMLHttpRequest擁有timeout屬性,其他瀏覽器沒有,但是XMLHttpRequest擁有一個abort方法,這個方法可以取消非同步請求,記住是非同步請求,同步操作這個方法無效,無效原因是同步提交下我們是沒機會執行該方法的。有了這個方法我們就可以類比ajax請求逾時。

  哦,寫了8000多字了,看來本篇也無法寫完我這個主題,由此可見頁面的提交資料方式的學問是很大的。

探求網頁同步提交、ajax和comet不為人知的秘密(中篇)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.