第十六章:腳本化HTTP

寫在本章內容前:javascript

第十五章:事件處理  涉及到到較多的文字篇幅,介於我的精力問題,暫不更新。主要包含的內容有事件類型、註冊事件處理程序、事件處理程序的調用、文檔加載事件、鼠標事件、鼠標滾輪事件、拖放事件、文本事件、鍵盤事件等9塊內容。感興趣的朋友能夠留言傳內容PDF。若是不着急的話,後期可能更新。敬請關注。

文本傳輸協議(HTTP)規定web瀏覽器如何從web服務器獲取文檔和向web服務器發送表單內容,以及web服務器如何響應這些請求和提交。web瀏覽器會處理大量的HTTP。一般,HTTP並不在腳本的控制下,只是當用戶單擊連接、提交表單和輸入URL時才發送。php

可是,用javascript代碼操縱HTTP是可行的。當腳本設置window對象的location屬性或調用表單對象的submit()方法時,都會初始化HTTP請求。在這種狀況下,瀏覽器會重新加載頁面。這種用腳本控制HTTP的方法在多框架頁面中很是有用,但這並不是咱們討論的主題。相反,這章討論在沒有致使web瀏覽器從新加載任何窗口或窗體內容的狀況下,腳本如何實現瀏覽器與服務器之間的通訊css

術語Ajax描述了一種主要使用腳本操做HTTP的web應用構架。(Ajax是Asynchronous javascript and XML的縮寫,這個術語由jesse James Carrett創造,最先出現於2005年2月它發佈的文章。「Ajax」曾經是一個流行多年的術語,如今它只不過是一個有用的術語,來描述腳本操縱HTTP請求的web應用構架)。Ajax應用的主要特色是使用腳本操做HTTP和web服務器進行數據交換,不會致使頁面重載。避免頁面重載(這是web初期的標準作法)的能力能使web應用感受更像傳統的桌面應用。web可使用Ajax技術把用戶的交互記錄數據記錄到服務器中;也能夠是簡單的顯示頁面,以後按需加載額外的數據和頁面組件來提示應用的啓動時間。html

Comet是和使用腳本操做HTTP的web應用構架相關的術語(Comet這個名字是Alex Russell在2006年3月創造,這個名字多是對Ajax開了個玩笑,Comet和Ajax都是美國的洗滌日用品牌)。在某種意義上,Comet和Ajax相反,在Comet中,web服務器發起通訊並異步發送到消息客戶端。若是web應用須要相應服務器發送消息,則它會使用Ajax技術發送或請求數據。在Ajax中,客戶端從服務器「拉」數據在Comet中,服務端向客戶端「推」數據。Comet還包括其餘名詞,如:「服務器推」,「Ajax推」,「HTTP流」。java

實現Ajax和Comet的方式有不少種,而這些底層的實現有時候稱爲傳輸協議(transport)。例如:<IMG>元素有一個src屬性。當腳本設置這個屬性爲url時,瀏覽器發起的HTTP請求會從這個URL下載圖片。所以腳本經過設置<img>元素的src屬性,且把信息圖片URL的查詢字符串部分,就把能通過編碼的信息傳遞給web服務器。web服務器實際上必須返回某個圖片做爲請求結果,但它必定要不可可見。例如一個1*1像素的透明圖片(這種類型的圖片也叫網頁信標(web bug)當網頁信標不是與當前網頁服務器而是其它服務器交流信息時,會擔憂隱私泄露。這種第三方的網頁信標方式經常使用於統計點擊數和網站流量分析)。程序員

<img>元素沒法實現完整的的Ajax傳輸協議,由於數據交換是單向的:客戶端能發送數據到服務器,但服務器的響應一直是張圖片致使客戶端沒法從中獲取提取信息。然而<iframe>元素更增強大,爲了把<iframe>做爲Ajax傳輸協議使用,腳本首先要把發送給web服務器的信息編碼到URL中,而後設置<iframe>的src屬性爲該URL。服務器建立一個包含響應內容的HTML文檔。並把它返回給web瀏覽器。而且在<iframe>中顯示它。<iframe>須要對用戶不可見。可使用css隱藏它。腳本能遍歷<iframe>的文檔對象來讀取服務端的響應,注意,這種方法受限於11.6.ii介紹的同源策略問題。web

實際上,<script>元素的src屬性能設置URL併發起HTTP GET請求,使用<script>元素實現腳本操縱HTTP是很是吸引人的。由於它們能夠跨域通訊並不受限於同源策略。一般,使用<script>的Ajax傳輸協議時,服務器的響應採用JSON(見6章9節)的數據格式,當知心腳本時,javascript解析器也能自動將其「解碼」。因爲它使用JSON數據格式,所以這種Ajax傳輸協議也叫「JSONP」。ajax

雖然在<iframe>和<script>傳輸協議上能實現AJAX技術,但一般還有更簡單的方式,一段時間來,全部的瀏覽器都支持XMLHttpRuquest對象,它定義了用腳本操做HTTP的API。除了經常使用的GET請求,這個API還包含實現POST請求能力,同時它能用文本或Document對象的形式返回服務器響應。雖然它的名字叫XMLHttpRequest API,但並沒限定只能使用XML文檔,他能獲取任意類型的文本文檔。本章第1節涵蓋XMLHttpRequestAPI和本章的大部分數據庫

章的大部分Ajax示例都將使用XMLHttpRequest對象實現協議(第1節)方案,咱們也將在本章第2節演示如何基於<script>的傳輸協議。由於<script>有規避同源限制的能力。編程

XML是可選的

「Ajax」中的X表示XML。這個HTTP(XMLHttpRquest)主要客戶端API在其名字中突出了XML,而且咱們在後面將看到XMLHttpRequest對象的一個重要屬性叫responseXML。它看起來像說明XML是腳本操縱HTTP的重要部分,但實際上不是。這些名字只是XML流行時的遺蹟。固然AJax吉祥能和XML文檔一塊兒工做。但使用XML只是一種選擇。實際上不多使用。XMLHttpRequest規範列出了這個使人困惑的名字的不足之處

  對象名XMLHttpRequest是爲了兼容web,雖然這個名字的每一個部分均可能形成誤導。首先,這個對象支持包括XML在內的任何基於文本的格式。其次,它能用於HTTP和HTTPS請求(一些實現支持除了HTTP和HTTPS以外的協議,但規範不包括這些功能)。最後它支持的請求是一個廣義的概念,指定是對定義的HTTP方法的設計HTTP請求或響應的全部活動。

Comet傳輸協議比Ajax更精妙,但須要客戶端和服務器創建(必要時從新創建)鏈接。同時要保存服務器鏈接處於打開狀態。這樣才能發送異步信息。隱藏的<irrame>能像Comet傳輸協議同樣有用。例如:服務器以<iframe>中執行的<script>元素的形式發生每條消息。實現Comet的一種更可靠的跨平臺方案是客戶端簡歷一個服務器鏈接(使用Ajax協議),同時服務器包裝這個鏈接打開直到它須要推送一條信息,服務器每發送一個信息就關閉這個鏈接。這樣就能夠確保客戶端正確接收到消息。處理該消息以後,客戶端立刻爲後續的消息推送創建一個新鏈接。

實現可靠的跨平臺Comet傳輸協議是很是有挑戰性的,因此大部分使用Comet構架的web應用開發者依賴像Dojo這樣的web框架中的傳輸協議。HTML5相關草案的Server-Sent事件,它用EventSource對象的形式定義了簡單的Comet API。本章第3節涵蓋EventSoure API而且演示了一個使用XMLHttpRequest顯示的簡單模擬示例。

在Ajax和Comet之上構建更高的通訊協議是可行的。例如,這些客戶端/服務器技術能夠用做RPC(Remote Procedure Call,遠程過程調用)機制或發佈/訂閱事件系統基礎。

本章不會介紹更高的協議,咱們的重點在能使Ajax和Comet可用的API上。

1.使用XMLHttpRequest

瀏覽器在XMLHttpRequest類上定義了它們的HTTP API。這個類的每一個實例都表示一個獨立的請求/響應對而且這個對象的屬性和方法容許指定請求細節和提取響應數據。不少年前web瀏覽器就開始支持XMLHttpRequest,而且其API已經到了W3C的最後制定標準的最後階段。同時W3C在制定「2級XMLHttpRequest」的標準草案。本節涵蓋XMLHttpRequest核心API。也包括當前至少被兩款瀏覽器支持的2級XMLHttpRequest標準草案(咱們稱爲XHR2)。

固然,使用這個HTTP API作的第一件事就是實例化XMLHttpRequest對象:

            var request = new XMLHttpRequest();

你也能夠重用已存在的XMLHttpRequest,但注意這將會終止以前經過該對象掛起的任何請求。

IE6中的XMLHttpRequest

微軟最先把XMLHttpRequest對象引入到IE5中,而且在IE5和6中只是一個Active對象。IE7以前的版本不支持非標準的XMLHttpRequest()構造函數,但能像如下來模擬解決:

             //ie5和i6模擬XMLHttpRequest()構造函數
            if (window.XMLHttpRequest === undefined) {
                window.XMLHttpRequest = function() {
                    try {
                        //若是可用,則使用Active對象的最新版本
                        return new ActiveXObject("Msxml2.XMLHTTP.6.0");
                    } catch (e1) {
                        try {
                            //不然回退早的版本
                            return new ActiveXObject("Msxml2.XMLHTTP.3.0");
                        } catch (e2) {
                            //不然,拋出錯誤
                            throw new Error("XMLHttpRequest is not supported");
                        }
                    }
                };
            }

一個HTTP請求由4個部分組成

  • HTTP請求的方法或「動做」(verb)
  • 正在請求的URL
  • 一個可選的請求頭集合。其中可能包含身份驗證
  • 一個可選的請求主體

服務器返回的HTTP請求響應包含3個部分:

  • 一個數字和文字組成的狀態碼,用來顯示成功和失敗
  • 一個響應頭集合
  • 響應主體

接下來兩節咱們將會展現如何設置HTTP請求的每一個部分和如何查詢HTTP響應的每一個部分。隨後的核心章節會涵蓋更多的專門議題。

HTTP的基礎請求/響應構架很是簡單並易用使用但在實踐中會有各類各樣的帶來複雜的問題客戶端和服務器交換cookie服務器重定向瀏覽器到其它服務器緩存某些資源而剩下的不緩存某些客戶端經過代理服務器發送全部的請求等。

XMLHttpRequest不是協議級的HTTP API,而是瀏覽器級的API,瀏覽器須要考慮cookie、重定向、緩存和代理,但代碼只須擔憂請求和響應

XMLHttpRequest和本地文件

網頁中可使用相對URL意味這咱們可使用本地文件系統來開發和測試HTML,來避免沒必要要的服務器端部署。然而在師院XMLHttpRequest進行Ajax編程時,這是不行的。XMLHttpRequest用於同HTTP和HTTPS協議一塊兒工做,理論上,它可以一樣像FTP這樣的協議一塊兒工做,好比請求方法和響應狀態碼等API都是HTTP特有的。若是從本地加載網頁。那麼該頁面中的腳本沒法經過相對的URL使用XMLHttpRequest,由於這些URL將相對於file://URL而不是http:// URL。而同源策略一般會阻止使用絕對的HTTP:// URL(見本小節的iiiiii小節)。結果是當使用XMLHttpRequest時,爲了測試他們一般把文件上傳到web服務器(或運行一個本地服務器)。

i.指定請求

建立XMLHttpRequest對象以後,發起HTTP請求下一步是調用XMLHttpRequest對象的open()方法去指定這個請求的兩個必須部分方法和URL

        request.open("GET",//開始一個HTTP GET請求
        "data.csv;") //URL的內容

open()的第一個參數指定HTTP方法或動做。這個字符串不區分大小寫,但一般你們使用大寫來匹配HTTP協議。「GET」和"POST"方法是普遍支持的。「GET」用於常規請求,它適用於當URL徹底指定請求資源,當請求對服務器沒有任何反作用以及當服務器響應是可緩存的「POST」方法經常使用於HTML表單。它在請求主體中包含額外數據(表單數據),且這些數據常存儲到服務器上的數據庫中(反作用)。相同的URL重複「POST」請求可能從服務器獲得的響應可能不一樣,同時不該該緩存使用這個方法的請求

除了GET和POST以外 ,XMLHttpRquest規範也把DELETE,HEAD,OPTIONS和PUT做爲open() 第一個參數。(HTTP CONNTECT TRACE TRACK由於安全風險已經被明確禁止)。舊的瀏覽器並不支持這些方法,但至少「HEAD」獲得普遍支持,本章有例子演示如何使用它。

open()第二個參數是URL。它是請求的主題。這是相對於文檔的URL,這個文檔包含調用open()的腳本。若是指定絕對URL、協議、主機和端口一般必須匹配所在文檔的對於內容:跨域請求一般會報錯(可是在服務器明確容許跨域時,2級XMLHttpRequest規範會容許它。第iiiiii小節)。

若是有請求頭的話,請求進程的下個步奏是設置它。例如,POST請求須要「Content-Type」頭指定請求主題的MIME類型。

            request.setRequestHeader("Content-Type", "text/plain");

若是相同的頭調用setRequestHeader()屢次,新值不會取代以前指定的值,相反,HTTP請求將包含這個頭的多個副本或這個頭將指定多個值。

你不能本身指定「Content-Length」、「Date」、「Referer」或「User-Agent」頭,XMLHttpRequest將自動添加這些頭防止僞造它們。相似地,XMLHttpRequest對象自動處理cookie、鏈接時間、字符集和編碼判斷,因此你沒法向setRequestHeader()傳遞這些頭。

Accept-Charset Content-Transfer-Encoding TE
Accepet-Encoding Date Trailer
Connection Expect Transfer-Encoding
Content-length Host Upgrad
cookie Keep-Alive User-Agent
cookie2 Referer Via

你能爲請求指定「Authorization」頭,但一般不須要這麼作。若是請求一個受密碼保護的URL,把用戶名和密碼做爲第4個和第5個參數傳遞給open()可選的第三個參數。可選的用戶名和密碼參數會在第4部分介紹。

使用XMLHttpRequest發起HTTP請求的最後一步是 指可選的請求主體並向服務器發送它。使用send()方法像以下這樣作:

request.send(null);

GET請求絕對沒有主體,全部應該傳遞null或省略這個參數。POST請求一般擁有主體,同事它應該配置使用setRequestHeader()指定的「Content-Type」頭。

順序問題

HTTP請求的各部分有指定順序:請求方法和URL首先到達而後是請求頭,最後是請求主體。XMLHTTPRequest實現一般調用send()方法開始啓動網絡。但XMLHTTPRequest API的設計彷佛使每一個方法都寫入網絡流。這意味這調用XMLHTTPRequest方法的順序必須匹配HTTP請求的構架。例如setRequestHeader()方法的調用必須在調用open()以前但在調用send()以後。不然它將拋出異常。

下面的例子調用了目前咱們介紹的全部XMLHttpRequest方法。它用POST方法發送文本字符串給服務器,並忽略服務器返回的任何響應。

             /**POST方法發送純文本給服務器**/
            function postMessage(msg) {
                var request = new XMLHttpRequest(); //新請求
                request.open("POST", "log.php"); //用POST向服務器端發送腳本
                //用請求主題發送純文本消息
                request.setRequestHeader("Content-type", //請求主體講述純文本
                    "text/plain;charset=UTF-8");
                request.send(msg);
                //請求完成 ,將忽略任何響應和錯誤
            }

上個例子的send()方法啓動請求,而後返回。當它等待的服務器的響應時間並不阻塞。接下來章節介紹的幾乎都是異步處理HTTP響應。

ii.取得響應

一個完整的HTTP響應由 狀態碼集合頭集合響應主體組成。這些均可以經過XMLHTTPRequest對象的屬性和方法使用

status和statusText屬性以數字和文本的形式返回HTTP狀態碼。這些屬性保存標準的HTTP值。像200和「OK」表示成功請求,404和「Not Found」表示URL不能匹配服務器上的任何資源

使用getResponseHeader()和getAllResponseHeaders()能查詢響應頭。XMLHttpRequest會自動處理cookie:它會從getAllResponseHeaders()頭返回集合中中過濾掉的cookie頭,而若是給getResponseHeader()傳遞「Set-Cookie」和「Set-cookie2」則返回true。

響應主體能夠從responseText屬性中獲得文本形式的,從responseXML屬性獲得Document形式的。(這個屬性名是有歷史性的:它實際上對XHTML和XML文檔有效,但XHR2說它也應該對普通的HTML文檔工做)關於responseXML的更多內容請看下面的小2節「響應解碼」節。

XMLHttpRequest對象常(除了見下面的「同步響應」節的內容)異步使用:發送請求後,send()方法當即返回,直到響應返回,前面列出的響應方法和屬性纔有效。爲了響應準備就緒時獲得通訊,必須監聽XMLHttpRequest對象上的readysyayechange事件(或者4小節描述的XHR進度事件)。但爲了理解這個事件類型,你必須理解readyState屬性

readyState是一個整數,它指定了HTTP請求的狀態。同時,下表列出了它的可能性的值。第一列符號是XMLHttpRequest構造函數定義的常量。這些常量是XMLHttpRequest規範的一部分,但老式的瀏覽器和IE8沒有定義他們。一般看到使用編碼值4來表示XMLRequest.DONE。

XMLHttpRequest的readyState值

常量 含義
UNSENT 0 open()還沒有調用
OPENED 1 open()已調用
HEADERS_RECEIVED 2 接收到頭信息
LOADING 3 接收到響應主體
DONE 4 響應完成

理論上,每次readState屬性改變都會觸發readystatechange事件。實際上當reayState改變爲0或1時可能沒觸發這個事件。當調用send()時,即便reayState仍處於OPEN狀態,也一般觸發它。某些瀏覽器在LOADING狀態是可以觸發屢次給出進度反饋。當readyState值改變爲4或服務器響應完成時,全部的瀏覽器都能觸發readystatechange事件。由於在響應完成以前也會觸發事件,因此事件處理程序應該一直校驗reayState值。

爲了監聽readystatechange事件,請把事件處理函數設置爲xmlhttprequest對象的onreadystatechange屬性。也能使用addEventListener()(在IE8以前版本中使用attachEvent()),但一般每一個請求只需求一個處理程序,因此只設置onreadystatechange更容易。

下面的例子定義了getText()函數來演示如何監聽reaystatechange事件。事件處理程序首先要確保請求完成。若是這樣,它會檢測響應狀態碼來確保請求成功。而後它查找「Content-Type」頭來驗證響應主體是不是指望類型。若是3個條件都獲得知足,它會把響應主體(以文本形式)發送給指定的回調函數。

             /*獲取HTTP響應的onreadysatechange*/
             //發出一個HTTP GET請求以得到指定URL內容
             //當響應成功到達,驗證它是不是純文本
             //若是是,把它傳遞給指定的回調函數
            function getText(url, callback) {
                var request = new XMLHttpRequest(); //建立新請求
                request.open("GET", url); //指定獲取URL
                request.onreadystatechange = function() { //定義事件處理程序
                    //若是請求完成,則它是成功的
                    if (request.readyState === 4 && request.status === 200) {
                        var type = request.getResponseHeader("Content-Type");
                        if (type.match(/^text/)) //確保響應是文本
                            callback(request.responseText); //把它傳遞給回調函數
                    }
                };
                request.send(null); //當即發送請求
            }

⑴.同步響應

因爲其自己的性質,異步處理HTTP響應是最好的方式。然而,XMLHttpRequest也支持同步響應。若是把false做爲第三個參數傳給open(),那麼send()方法將阻塞直到請求完成。在這種狀況下,不須要使用事件處理程序:一旦send()返回,僅須要檢查XMLHttpRequest對象的status和responseText屬性。比較上例子的getText()函數同步代碼:

             //發起同步的HTTP GET請求以得到指定URL的內容
             //返回響應文本,或若是請求不成功或響應不是文本就報錯
            function getTextSync(url) {
                var request = new XMLHttpRequest(); //建立新請求
                request.open("GET", url, false); //傳遞false實現同步
                request.send(null); //當即發送請求
                //若是請求不是200 OK,就報錯
                if (request.status !== 200) throw new Error("request.statusText");
                //若是類型錯誤,就報錯
                var type = request.getResponseHeader("Content-Type");
                if (!type.match(/^text/))
                    throw new Error("Expected texttual response: " + type);
                return request.responseText;
            }

同步請求是吸引人的,但應該避免使用它們。客戶端javascript是單線程的,當send()方法阻塞時,它 一般致使整個瀏覽器UI凍結。若是鏈接的服務器 響應慢,那麼用戶的瀏覽器凍結。(然而,參加20章4小節能夠接受使用同步的請求的場景。)

⑵.響應解碼
在前面的示例中,咱們假設服務器器使用像「text/plain」、「text/html」、或「text/css」這樣的MIME類型發送文本響應,而後咱們使用XMLHTTPRequest對象的reponseText屬性到它。
可是還有其它方式來處理服務器的響應。若是服務器發送XML或XHTML文檔將其響應,你可能經過responseXML屬性得到一個解析形式的XML文檔。這個屬性的值是一個Document對象,可使用13章介紹的技術搜索和遍歷它。(XHR2草案規範之處瀏覽器也應該自動解析「text/html」類型的響應,使他們也能經過responeXML屬性獲取其Document文檔對象)

若是服務器想發送諸如對象或數組這樣的結構化數據做爲其響應,它應該傳輸JSON編碼(6章9節)的字符串。當接受它時,能夠把responseText屬性傳給JSOP.parse()。下面的例子是上面的一個例子的概括:它實現指定URL的GET請求並當URL的內容準備就緒時把他們傳遞給指定的回調函數。但它不是一直傳遞文本,而是傳遞Document對象或使用JSON.parse()編碼對應的對象或字符串。

             /**解析HTTP響應**/
             //發起http get響應以獲取指定url內容
             //當響應到達時,把它以解析後的XML Document對象、解析後的JSON對象或字符串的形式傳遞給回調函數
            function get(url, callback) {
                var request = new XMLHttpRequest();
                request.open("GET", url); //建立新請求
                request.onreadystatechange = function() { //定義事件監聽器
                    //若是請求完成且成功
                    if (request.readyState === 4 && request.status === 200) {
                        //獲取響應的類型
                        var type = request.getAllResponseHeaders("Content-Type");
                        //檢測類型,這樣咱們不能再將帶獲得HTML文檔
                        if (type.indexOf("xml") !== -1 && request.responseXML)
                            callback(request.responseXML); //Document對象響應
                        else if (type === "application/json")
                            callback(JSON.parse(request.responseText)); //JSON響應
                        else
                            callback(request.responseText); //字符串響應
                    }
                };
                request.send(null); //當即發送
            }

 上面的該響應的「Content-Type」頭且專門處理「application/json」影響。你可能但願特殊的編碼的另外一個響應是「application/javascript」或「text/javascript」。你能使用XMLHttpRequest請求Javascript腳本,而後使用全局eval()(參見4章12.2節)執行這個腳本。可是,在這種狀況下不須要使用XMLHttpRequest對象,由於<script>元素本省操做HTTP的腳本的能力徹底能夠實現加載並執行腳本。要記住,<script>元素能發起跨域HTTP請求,而XMLHttpRequest API則禁止

web服務端一般使用二進制數據(好比圖片文件)響應HTTP請求responseText屬性只能用於文本,且它不能妥善處理二進制響應,即便對最終字符串使用了charCodeAt()方法,XHR2定義了處理二進制響應,即便對最終字符串使用了CharCodeAt()方法。XHR2定義了處理二進制響應的方法。

服務器響應的正常解碼是假設服務器爲這個響應發送了"Content-Type"頭和正確的MIME類型。例如,若是服務器發送了XML文檔但沒有設置適當的MIMIE類型,那麼XMLHttpRequest對象將不會解析它且設置responseXML屬性。或者,若是服務器在「Content-Type」頭中包含了錯誤的「charset」參數,那麼XMLHttpRequest將使用錯誤的編碼來解析響應,而且responeText的字符串多是錯的。XHR2定義了overrideMimeType()方法來解決這個問題,而且大量的瀏覽器已經實現了它。若是想對服務器你更瞭解資源的MIME類型,那麼在調用send()以前把類型傳遞給overrideMimeType()。這將使XMLHttpRequest忽略「Content-Type」頭且使用指定的類型。假設你將下載XML文件,而你計劃將它當成純文本對待。可使用setOverrideMimeType()讓XMLHttpRequest知道它不須要把文件解析成XML文檔。

            //不要把響應做爲XML文檔處理
            request.overrideMimeType("text/plain;charset=utf-8")

iii.編碼請求主體

HTTP POST請求包含一個請求主體,它包含客戶端傳遞給服務器的數據。在postMessage()例子中,請求主體是簡單的文本字符串,可是咱們一般使用HTTP請求發送的都是更復雜的數據。本節演示這樣作的一些方法。

⑴表單編碼的請求

考慮HTML表單。當用戶提交時,表單中的數據(每一個表單元素的名字和值)編碼到一個字符串中並隨請求發送。默認的狀況下,HTML表單經過POST方法發送給服務器,而編碼後的表單數據則用作請求主題。對錶單數據使用的編碼方案相對簡單:對每一個表單元素的名字和執行普通的URL編碼(使用十六進制轉義碼替換特殊字符串),使用等號後編碼後的名字和值分開,並使用「&」符號分開名/值對。一個簡單的編碼以下這樣:

             find=laobeijing&mendian=3123&radius=1km

表單數據編碼格式有一個正式的MIME類型

        application/x-www-form-urlencoded

當使用POST方法提交這種順序的表單數據時,必須設置"Content-Type"請求頭爲這個值。

注意。這種類型的編碼並不須要HTML,在本章咱們實際上將不須要直接使用表單。在Ajax應用中,你但願發送給服務器的極可能是一個javascript對象。(這個對象可能從HTML表單的用戶輸入中獲得,但這裏不是問題),前面展現的數據變成javascript對象的表單的編碼形式多是:

            {
                find: "laobeijing",
                mendian: 3123,
                radius: "1km"
            }

表單編碼在web上是如此普遍使用,同時全部服務器端的編程語言都能獲得良好的支持,全部非表單的數據的表單編碼一般也是容易實現的事情。下面的例子展現瞭如何實現對象屬性的表單的編碼。

             /**用於HTTP請求的編碼對象**/
            /**
             * 編碼對象的屬性
             * 若是它們是來自HTML表單的名/值對,使用application/x-www-form-urlencode格式
             **/
            function encodeFormDate(data) {
                if (!data) return ""; //一直返回字符串
                var pair = []; //爲了保存名=值對
                for (var name in data) { //爲了每一個名字
                    if (!data.hasOwnProperty(name)) continue; //跳過繼承屬性
                    if (typeof data[name] === "function") continue; //跳過方法
                    var value = data[name].toString(); //把值轉化爲字符串
                    name = encodeURIComponent(name.replace("%20", "+")); //編碼名字
                    value = encodeURIComponent(value.replace("%20", "+")); //編碼值
                    pair.push(name + "=" + value); //記住名對
                }
                return pair.join("&"); //返回使用「&」鏈接的名/值
            }

使用已經定義的encodeFormData()函數,咱們能容易的寫出像下面的例子中的postData()函數這樣的工具函數,須要注意的是,簡單來講,postData()函數(在隨後的示例中有類似的函數)不能處理服務器響應。當響應完成後,它傳遞整個XMLHttpRequest對象給指定的函數。這個回調函數賦值檢測響應狀態碼和提取響應文本。

             /**使用表單編碼數據發起一個HTTP POST請求**/
            function postData(url, data, callback) {
                var request = new XMLHttpRequest;
                request.open("POST", url);
                request.onreadystatechange = function() { //簡單的事情處理程序
                    if (request.readyState === 4 && callback) //當響應完成
                        callback(request); //調用回調函數
                };
                request.setRequestHeader("Content-type", //設置Content-type
                    "application/x-www-form-urlencoded");
                request.send(encodeFormData(data)); //發送表單編碼數據
            }

表單數據一樣能夠經過GET請求來提交,既然表單提交的目的是爲了執行只讀查詢,所以GET請求比POST更合適。(當提交表單的目標僅僅是一個只讀查詢,GET比POST更合適)GET請求歷來沒有主體,因此須要發送給服務器的表單編碼數據「負載」須要一個URL(後跟一個問號)的查詢部分。encodeFormData()工具函數也能用於這種GET請求。下面的例子演示瞭如何使用它。

             /**使用表單數據發起GET請求**/
            function getData(url, data, callback) {
                var request = new XMLHttpRequest();
                request.open("GET", url +
                    "?" + encodeFormData(data)); //經過添加編碼數據獲取指定url
                request.onreadystatechange = function() { //簡單事件處理程序
                    if (request.readyState === 4 && callback) callback(request);
                };
                request.send(null); //發送請求
            }

HTML表單在提交的時候會對錶單數據進行URL編碼,但使用XMLHttpRequest能給咱們編碼本身想要的任何數據。隨着服務器上的適當支持。咱們的laobeijing查詢數據將編碼成一個清晰的url,以下

        http://www.a.com/01234/1km/mendian

 ⑵JSON編碼的請求

在POST請求主體中使用表單編碼是常見慣例,但在任何狀況下它都不是HTTP協議的必需品。近年來,做爲web交換格式的JSON已經獲得普及。下面的例子展現了如何使用JSON.stringfy()(參見6章9節)編碼主體。注意這個例子和上上個例子postData()不一樣僅在最後兩行

             /**使用JSON編碼主體來發起HTTP POST請求**/
            function postJSON(url, data, callback) {
                var request = new XMLHttpRequest();
                request.open("POST", url); //對指定的URL發送POST請求
                request.onreadystatechange = function() { //簡單的事件處理程序
                    if (request.readyState === 4 && callback) //當響應完成時
                        callback(request); //調用回調函數
                };
                request.setRequestHeader("Content-Type", "application/json");
                request.send(JSON.stringify(data));
            }

⑶XML編碼的請求

XML有時候也用於數據傳輸的編碼。javascript對象的用表單編碼或JSON編碼表達的是laobeijing查詢,也能用XML文檔來表示它。例如,以下所示:

        <query>
            <find miandian="3123" radius="1km">
                laobeijing
            </find>
        </query>

在目前展現的例子中,XMLHttpRequest的send()方法的參數是一個字符串或null。實際上,這裏能夠傳入XML Document對象。下面的例子展現瞭如何建立一個簡單的XML Document對象並使用它做爲HTTP請求的主體

             /**使用XML文檔做爲其主體的HTTP POST請求**/
             //在XML編碼什麼東西,在哪兒,半徑, 而後向指定的URL和POST請求
             //收到響應時,回調函數
            function postQuery(url, what, where, radius, callback) {
                var request = new XMLHttpRequest();
                request.open("POST", url); //對指定的URL發送POST請求
                request.onreadystatechange = function() { //簡單的事件處理程序
                    if (request.readyState === 4 && callback) callback(request);
                };
                //新建XML文檔
                var doc = document.implementation.createDocument("", "query", null);
                var query = doc.documentElement; //<query>元素
                var find = document.createElement("find"); //<find>元素
                query.appendChild(find); //添加到query中
                find.setAttribute("laobeijing", where); //設置find屬性
                find.setAttribute("radius", radius);
                find.appendChild(doc.createTextNode(what)); //並設置<find>內容
                //如今向服務器發送xml編碼的數據
                //注意,將自動設置Content-Type頭
                request.send(doc);
            }

注意,上面的例子未曾爲請求設置"Content-Type"頭。當給send()方法傳入xml文檔時,並無預先指定"Content-Type"頭,但XML對象會自動設置一個合適的頭。(相似的,若是給send()傳入一個字符串,但沒有指定Content-Type頭,那麼XMLHttpRequest將會添加"ext/plain;charset=UTF-8"頭)在本章最先的那個代碼演示中顯式的設置了這個頭,實際上對純文本請求主體並不須要這麼作。

 ⑷上傳文件

HTML表單的特性之一是當用戶經過<input type="file">元素選擇文件時,表單將在它產生的POST請求主題中發生文件內容。HTML表單始終能上傳文件,但到目前爲止,還不能使用XMLHttpRequest API作相同的事情。而後XHR2容許向send()方法傳入File對象來實現上傳文件。

沒有File()對象構造函數,腳本僅能得到表示用戶當前選擇的的File對象。在支持File對象的瀏覽器中,每一個<input type="file">元素有一個files對象。它是File對象中的類數組對象。拖放API(參加15.7節)容許經過拖放事件的dataTeansfer.files屬性訪問用戶的「拖放」到元素上的文件。咱們將在20.6節和20.7節看到更多關於File對象的內容。但如今來說,能夠將它當作一個用戶選擇文件徹底不透明的表示形式,適用於經過send()來上傳文件。下面的例子是一個天然的javascript函數,它對某些文件上傳元素添加了change事件處理程序,這樣他們能自動把任何選擇過的文件內容經過POST方法發送到指定的URL

            /**使用http POST請求上傳文件**/
            //查找data-uploadto屬性的所有<input type="file">元素,並主持onchange事件處理程序
            //這樣任何選擇的文件都會自動經過POST方法發送到指定的"uploadto"url
            //服務器的響應是忽略的
            whenReady(function(){
                var elets = document.getElementsByTagName("input"); //全部 的input元素
                for(var i = 0 ;i<elets.length;i++){//遍歷它們
                    var input = elets[i];
                    if(input.type !== "file") continue; //跳過非文件的上傳元素
                    var url = input.getAttribute("data-upload");//獲取上傳url
                    if(!url) continue;//跳過任何沒有url的元素
                    
                    input.addEventListener("change",function(){//當用戶選擇文件時
                        var file = this.files[0]; //假設單個文件選擇
                        if(!file) return; //若是沒有任何文件 不作任何事情
                        var xhr = new XMLHttpRequest(); //建立新請求
                        xhr.open("POST",url);//向這個URL發送POST請求
                        xhr.send(file); //把文件做爲主體發送
                    },false);
                }
            });

文件類型是更通用的二進制大對象(Blob)類型的一個子類型,XHR2容許向send()方法傳入任何Blob對象。若是沒有設置Content-Type頭,這個Blob對象的type屬性用於設置等待上傳的Content-Type頭若是須要上傳已經產生二進制數據,可使用20章第5節和20章第6節3小節展現的技術把數據抓化爲Blob並將其做爲請求主體

⑸multipart/form-data請求

當HTML表單同時包含上傳元素和其餘元素時,瀏覽器不能使用普通的表單碼而必須使用稱爲"multipart/form-data"的特殊Content-type採用POST方法提交表單。這種編碼包括使用長「邊界」字符串把請求的主體分離成多個部分。對於文本數據,手動建立"multipart/form-data"請求主體是可能的,但很複雜。

XHR2定義了FormData API。它容易實現多部分請求主體。首先,使用FormData()構造函數建立FormData對象,而後按需調用對象的append()方法把個體的「部分」(能夠是字符串,File,或Blob對象)添加到請求中。最後,把FormData對象傳遞給send() 方法。send()方法將對請求定義合適的邊界字符串和設置「Content-Type」頭。

下面的連續兩個例子演示了FormData使用。

             /**使用POST方法發送multipart/form-data請求主體**/
            function postFormData(url, data, callback) {
                if (typeof FormData === "undefined")
                    throw new Error("FormData is not implemented");
                var request = new XMLHttpRequest(); //新http請求
                request.open("POST", url); //使用指定的url發送post請求
                request.onreadystatechange = function() { //簡單事件處理程序
                    if (request.readyState === 4 && callback) //當響應完成時
                        callback(request); //調用回調函數
                };
                var formdata = new FormData();
                for (var name in data) {
                    if (!data.hasOwnProperty(name)) continue; //跳過繼承方法
                    var value = data[name];
                    if (typeof value === "function") continue; //跳過方法
                    //每一個屬性變成請求的一個部分
                    //這裏容許file屬性
                    formdata.append(name.value); //做爲一部分添加名/值對
                }
                //在multipart/form-data請求主體中發送名/值對
                //每對都是請求的一部分,注意,當傳入FormData對象時
                //send()會自動設置Content-Type頭
                request.send(formdata)
            }

iiii.HTTP進度事件

在以前的示例中,使用readystatechange事件探測HTTP請求的完成。XHR2規範草案定義了有更多有用的事件集,已經在現代主流的瀏覽器中獲得了支持。這個新的事件模型中,XMLHttpRequest對象在請求的不一樣階段觸發不一樣的事件,因此它不須要檢查reayState屬性

在支持它們的瀏覽器中,這些新事件會像以下這樣觸發。當調用send()時,觸發單個loadstart事件。當正在加載服務器的響應時,XMLHTTPRequest對象會發生progress事件,一般每隔50秒左右,因此可使用這些事件給用戶反饋請求的進度。若是請求快速完成,它可能從不會觸發progress事件。當事件完成,會觸發load事件。

一個完成的請求不必定是成功的請求,例如,load事件的處理程序應該檢查XMLHttpRequest對象的status狀態碼來肯定收到的是「200 OK」而不是「404 Not Found」的HTTP響應。
HTTP請求沒法完成有3種狀況,對應3種事件。若是請求超時,會觸發timeout事件。若是請求終止,會觸發abort事件。(16章1節iiiii包含超時和abort方法的內容。)最後,像太多重定向這樣網絡錯誤會阻止請求完成,但這些狀況發生時會觸發error事件

對應任何具體請求,瀏覽器將只會觸發load、abort、timeout、error、事件中的一個。XHR2規範草案指出一旦這些事件中的一個發生後,瀏覽器會觸發loadend事件

能夠經過XMLHTTPRequest對象的addEventListener()方法爲這些progress事件中的每一個都註冊處理程序

若是每種事件只有一個事件處理程序,一般更容易的方法是隻設置對於處理程序屬性,好比onprogress和onload。甚至可使用這些事件屬性是否來存在來測試瀏覽器是否支持progress事件:

            if("onprogress" in (new XMLHttpRequest())){
                console.log("good!") //支持progress事件
            }

除了像和timestamp這樣經常使用的Event對象屬性外,與這些progress事件相關聯的事件對象還有3個有用的屬性loaded屬性是目前傳輸的字節數值。total屬性是自「Content-Length」頭傳輸的總體長度(單位是字節),若是不知道內容長度則爲0。最後,若是知道內容長度則lengthComputable屬性爲true;不然爲false。顯然,total和loaded屬性對progress事件處理程序至關有用

            request.onprogress = function(e) {
                if (e.lengthComputable)
                    progress.innerHTML = Math.round(100 * e.loaded / e.total) + "% Complete";
            }

上傳進度事件

除了爲監控HTTP響應的加載定義的這些有用的事件外,XHR2也給出了用於監控HTTP請求上傳的事件。在實現這些特性的瀏覽器中,XMLHttpRequest對象將有upload屬性。upload屬性值是一個對象,它定義了addEventListener()方法和整個的propress事件集合,好比onprogress和onload。(但upload對象沒有定義onreadystatechange屬性,upload僅能觸發新的事件類型)

你能僅僅像使用常見的progeress事件處理程序同樣使用upload事件處理程序。對於XMLHttpRequest對象x,設置x.onprogress以響應下載進度,並設置x.upload.onprogress以健康請求的上傳進度。

下面的例子使用了upload progress事件把上傳進度反饋給用戶。這個示例也展現瞭如何拖放API中得到File對象和如何使用FormData API在單個XMLHTTPRequest請求中上傳多個文件。

             /**監控HTTP上傳進度**/
             //查找全部包含"fileDropTarget"類的元素
             //並註冊DnD事件處理程序使它們可以響應文件的拖放
             //當文件放下時,上傳它們到data-uploadto屬性指定的url
            wenReady(function() {
                var elts = document.getElementsByClassName("fileDropTarget");
                for (var i = 0; i < elts.length; i++) {
                    var target = elts[i];
                    var url = target.getAttribute("data-uploadto");
                    if (!url) continue;
                    createFileUploadDropTarget(target, url);
                }

                function createFileUploadDropTarget(target, url) {
                    //跟蹤當前是否這個在上傳,所以咱們能拒絕放下
                    //咱們能夠處理多個併發上傳
                    //但這個例子使用進步通知太難了
                    var uploading = false;
                    console.log(target, url);
                    target.ondragenter = function(e) {
                        console.log("dragenter");
                        if (uploading) return; //如正在忙,忽略拖放
                        var types = e.dataTransfer.types;
                        if(types &&
                        ((types.contains && types.contains("Files"))||
                        (types.indexOf && types.indexOf("Files") !== -1))){
                            target.classList.add("wantdrop");
                            return false;
                        }
                    };
                    target.ondragover = function(e){if(!uploading) return false;};
                    target.ondragleave = function(e){
                        if(!uploading) target.classList.remove("wantdrop");
                    };
                    target.ondrop = function(e){
                        if(!uploading) return false;
                        var files = e.dataTransfer.files;
                        if(file && file.length){
                            uploading = true;
                            var message = "Uploading file:<ul>";
                            for(var i = 0; i<files.length; i++)
                            message += "<li>" + files[i].name + "</li>";
                            message += "</ul>";
                            
                            target.innerHTML = message;
                            target.classList.remove("wantdrop");
                            target.classList.add("uploading");
                            
                            var xhr = new XMLHttpRequest();
                            xhr.open("POST",url);
                            var body = new FormData();
                            for(var i = 0; i<files.length;i++)body.append(i,files[i]);
                            xhr.upload.onprogress  = function(e){
                                if(e.lengthComputable){
                                    target.innerHTML = message + 
                                    Math.round(e.loaded/e.total*100)+
                                    "% Complete";
                                }
                            };
                            xhr.upload.onload = function(e){
                                uploading = false;
                                target.classList.remove("uploading");
                                target.innerHTML = "Drop files to upload";
                            };
                            xhr.send(body);
                            return false;
                        }
                        target.classList.remove("wantdrop");
                    }
                }
            });

 iiiii.停止請求和超時

能夠經過調用XMLHTTPRequest對象的abort()方法來取消正在進行的HTTP請求。abort()方法在全部的XMLHttpRequest版本和XHR2對象中可用。調用abort()方法在這個對象上觸發abort事件。(判斷瀏覽器支持abort事件,能夠經過XMLHTTPRequest對象的"onabort"屬性是否存在來判斷。)

調用abort()的主要緣由是完成取消或超時請求消耗的時間太長或響應變得無關時。假設使用XMLHttpRequest爲文本輸入域請求自動完成推薦。若是用戶在服務器的建議達到以前輸入了新字符,這時等待請求不在有趣,要停止。

XHR2定義了timeout屬性來指定請求自動停止後的毫秒數,也定義了timeout事件用於當超時發生時的觸發(不是abort事件)。在本書寫做時,瀏覽器不支持這些自動超時(而且他們的XMLHttpRequest對象沒有timeout和ontimeout屬性).能夠用setTimeout()(參加12.1節)和abort()方法實現本身的超時。下面的例子演示瞭如何這麼作

             /**實現超時**/
             //發起HTTP GET請求獲取指定URL內容
             //若是響應成功到達,傳入responseText給回調函數
             //若是響應在timeout毫秒內沒有到達,停止這個請求
             //瀏覽器可能在abort()後出發「readystatechange」
             //若是是部分請求結果到達,甚至可能設置status屬性
             //因此要設置一個標記,當部分且超過的響應到達時不會調用回調函數
             //若是使用load事件就沒有這個風險
            function timeGetText(url, timeout, callback) {
                var request = new XMLHttpRequest();
                var timedout = false; //是否超時
                //啓動計時器,在timeout毫秒鼠後將停止請求
                var timer = setTimeout(function() { //若是觸發,啓動一個計時器
                    timeout = true; //設置標記
                    request.abort(); //設置停止請求
                }, timeout); //停止請求以前的時長
                request.open("GET", url); //獲取指定的url
                request.onreadystatechange = function() { //定義事件的處理程序
                    if (request.readyState !== 4) return; //忽略未完成的請求
                    if (timedout) return; //忽略停止請求
                    clearTimeout(timer); //取消等待的超時
                    if (request.status === 200) //若是請求層高
                        callback(request.responseText); //把response傳給回調函數
                };
                request.send(null); //當即發送請求
            }

            function callback() {console.log("成功")};
            timeGetText("index.html", 10000, callback); //此處測試不跨域

iiiiii.跨域HTTP請求

做爲同源策略的一部分,XMLHTTPRequest對象一般僅跨域發起和文檔具備相同服務器的HTTP請求。這個如今關閉了安全漏洞,但它也笨手笨腳且阻止了大量合適的跨域請求。跨域在<form>和<iframe>元素中使用跨域URL,而瀏覽器顯示最終的跨域文檔。但由於同源策略,瀏覽器不容許原始腳本查找跨域文檔的內容。使用XMLHttpRequest,文檔內容都是經過responseText屬性暴露,全部同源策略不容許XMLHTTPRequest進行跨域請求。(注意<script>元素並未真正受限於同源策略:它加載並執行任何來源的腳本。若是咱們看16.2節,跨域請求的靈活性是的<script>元素成爲取代XMLHTTPRequest的主流Ajax傳輸協議

XHR2經過HTTP響應中選擇發送合適的CORS(跨域資源共享cross origin resoure sharing),容許跨域訪問網站。Firefox,Safari,Chrome當前版本都支持CORS,而IE8經過這裏沒有列出專業的XDomianRequest對象支持它。做爲WEB程序員,使用這個功能並不須要額外的工做:若是瀏覽器支持XMLHTTPRequest的CORS且容許跨域請求,那麼同源策略將不放寬而跨域請求就會正常工做。

雖然實現CORS支持的跨域請求工做不須要作任何事情,但有一些安全細節須要瞭解,首先若是給XMLHTTPRequest的opne()方法傳入用戶名和密碼,那麼它們絕對不能經過跨域請求發送(這使得分佈式密碼攻擊稱爲可能)除外跨域請求一般也不會包含其它任何用戶證書:cookie和HTTP身份驗證令牌(token)一般也不會做爲請求內容的部分發送且做爲跨域響應來接受的cookie都會丟棄若是跨域請求須要這幾張憑證纔可以成功,那麼必須在用send()發送請求錢設置XMLHttpRequest的withCredentials屬性爲true。這樣作不常見,但測試withCredentials的存在性是測試瀏覽器是否支持CORS的一種反方法

下面的例子是常見的javascript代碼,它是的XMLHTTPRequest實現HTTP HEAD請求如下載至文檔中<a>元素連接資源的類型、大小和時間等信息。這個HEAD請求按需發起,且由此產生的連接信息會出如今工具提示中,這個示例的假設跨域連接的信息不可用,但經過支持CORS的瀏覽器會嘗試下載它。

             /**使用HEAD和CORS請求連接詳細信息**/
            /**
             * linkdetails.js
             * 這個常見的javascript模塊查詢有href屬性但沒有title屬性的全部<a>元素
             * 並給他們主場onmouserover事件處理程序
             * 這個事件處理程序使用XMLHttpRequest HEAD請求連接資源的詳細信息
             * 而後把這些詳細信息設置爲連接的title屬性,這樣他會在工具提示中顯示
             **/
            whenReady(function() {
                //是否有機會使用跨域請求?
                var supportsCORS = (new XMLHttpRequest()).withCredentials !== undefined;
                //console.log(supportsCORS)
                var links = document.getElementsByTagName('a');
                for (var i = 0; i < links.length; i++) {
                    var link = links[i];
                    if (!link.href) continue; //跳過沒有超連接的錨點
                    if (link.title) continue; //跳過已經有的工具提示的連接
                    //若是這是一個跨域連接
                    if (link.host !== location.host || link.protocol !== location.protocol) {
                        link.title = "站外連接"; //假設咱們不能獲得任何信息
                        if (!supportsCORS) continue; //若是沒有CORS支持就退出
                        //不然,咱們能瞭解這個連接的更多信息
                        //不然,繼續,註冊事件處理辰星,因而咱們能夠嘗試
                    }
                    //註冊事件處理程序,當鼠標懸停時下注詳細信息
                    if (link.addEventListener)
                        link.addEventListener("mouseover", mouseoverHandler, false);
                    else
                        link.attachEvent("onmouseover", mouseoverHandler);
                }

                function mouseoverHandler(e) {
                    var link = e.target || e.srcElement; //<a>元素
                    var url = link.href; //連接url
                    
                    var req = new XMLHttpRequest();
                    req.open("HEAD",url); //僅僅詢問頭信息
                    req.onreadystatechange = function(){ //事件處理程序
                        if(req.readyState !== 4) return; //忽略未完成的請求
                        if(req.status === 200){//若是成功
                            var type = req.getResponseHeader("Content-type");//獲取連接的詳細狀況
                            var size = req.getResponseHeader("content-Length"); 
                            var date = req.getResponseHeader("Last-Modified");
                            //在工具提示中顯示詳細信息
                            link.title ="類型" + type + "\n" + 
                            "大小" + size + "\n" + "時間:" + date;
                        }
                        else{
                            //若是請求失敗,且連接沒有「站外連接」的工具提示
                            //版顯示這個錯誤
                            if(!link.title)
                            link.title = "could not fetch details:\n" +
                            req.status + " " + req.statusText;
                        }
                    };
                    req.send(null);
                                
                    //移除處理程序:僅想一次獲取這些頭信息
                    if(link.removeEventListener)
                    link.removeEventListener("mouseover",mouseoverHandler,false);
                    else
                    link.detachEvent("mouseover",mouseoverHandler);
                }
            });

 2.借用<script>發送HTTP請求:JOSNP

本章概述提到過<script>元素能夠做爲一種ajax傳輸機制:只需設置<script>元素的src屬性(假如它還沒插入到document中,須要插入進去),而後瀏覽器就會發送一個HTTP請求如下至src屬性所指向的url。使用<script>元素進行ajax傳輸的一個主要緣由是,它不受同源策略的影響,所以,能夠它能夠從其它服務器請求數據第二個緣由是包含JSON編碼數據的響應體會自動解碼(即,執行)

爲了使用<script>元素進行AJAX傳輸,必須容許web頁面可執行遠程服務器發送過來的任何javascript代碼。這意味着對不可信的服務器,不該該採起此措施。當與可信 的服務器通訊時,要堤防攻擊者接管你的網頁,運行本身的代碼。顯示本身的想要的內容,這表現的內容就像是來自於你的網站。

這種使用<script>元素做爲Ajax傳輸的技術稱爲JSONP。若響應的數據是通過json編碼的,則適合使用該技術。P表明「填充」或「前綴」。

假設你已經寫過一個服務,它處理GET請求並返回JSON編碼的數據。同源的文檔能夠在代碼中使用XMLHttpRequest和JSON.parse()。當經過<script>元素調用數據是,響應內容用javascript函數名和圓括號包裹起來。而不是發送這樣的一段JSON數據。

        [1,2{"bukle":"my shoes"}]

它會發送這樣的一個包裹後的JSON響應:

            handleResponse([1, 2 {
                "bukle": "my shoes"}]
            )

包裹後的響應成爲javascript內容,他它先判斷JSON編碼後的數據(畢竟就是一個javascript表達式),而後把它傳遞給handleResponse()函數,咱們能夠假設文檔拿這些數據會作一些有用的事情。

爲了可行起見,咱們必須經過某種方式告訴服務,它正在從一個<javascript>元素調用,必須返回JSONP響應,而不該該是普通的JSON響應。這個能夠經過URL中添加一個參數來實現,例如:追加「?json」(或&json),在實踐中,支持JSONP的服務不會強制指定客戶端筆仙實現的回調函數名稱,好比handleResponse。相反,他們使用查詢參數的值,容許客戶端指定一個函數名,而後使用函數名去填充響應。下面的例子使用了一個名爲jsonp的查詢參數來指定回調函數的名稱。許多支持JSONP的服務都能分辨出這個參數名。另一個常見的參數名稱是callback,爲了讓使用到的服務支持相似特殊的需求,就須要在代碼上作一些修改了。

下面的例子定義了一個getJSOP()函數,它發送JSONP請求。這個例子有點複雜,有幾點值得注意。首先,注意它是如何建立一個新的<Script>元素,設置其URL,並將它插入到文檔中。正是插入操做觸發HTTP請求。其次,注意下面的的例子爲每一個請求都建立了一個全新的內部回調函數,回調函數做爲getJSONP()函數的一個屬性存儲起來。最後要注意的是回調函數作了一些清理工做:刪除腳本元素,並刪除自身

             /**使用script元素髮送JSOP請求**/
             //根據指定的URl發送一個JSONP請求
             //而後把解析獲得的響應數據傳遞給回調函數
             //在URL中添加一個名爲jsonp的查詢參數,用於指定該請求回調函數的名稱。
            function getJSONP(url, callback) {
                //爲請求建立一個惟一的回調函數名稱
                var cbnum = "cb" + getJSONP.counter++; //每次自增計數器
                var cbname = "getJSONP." + cbnum; //做爲JSONP函數的屬性
                
                //將回調函數名稱以表單編碼的形式添加到URL中的查詢部分中
                //使用jsonp做爲參數名,一些支持JSONP的服務
                //可能使用其餘參數名,好比callback
                if (url.indexOf( ? ) === -1) //URL沒有查詢的部分
                    url += "?jsonp=" + cbname; //做爲查詢部分添加參數    
                else
                    url += "&jsonp=" + cbname; //做爲新的參數添加它
                
                //建立script元素用於發送請求
                var script = document.createElement("script");
                
                //定義將被腳本執行的回調函數
                getJSONP[cbnum] = function(response){
                    try{
                        callback(response);//處理響應數據
                    }
                    finally{
                        delete getJSONP[cbnum];//刪除該函數
                        script.parentNode.removeChild(script);//移除script元素
                    }
                };
                
                //當即觸發HTTP請求
                script.src = url; //設置腳本的url
                document.body.appendChild(script);//把它添加到文檔中
            }
            getJSONP.counter = 0;//用於建立惟一回調函數名稱的計數器

3.基於服務器推送的Comet技術

(暫時不更新)

(歡迎你們關注本章內容,也能夠關注上上章內容:第十四章 校本化CSS  )

下一章:第十七章:jQuery類庫

相關文章
相關標籤/搜索