第十六章:腳本化HTTP


寫在本章內容前:

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

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

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

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

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

實現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介紹的同源策略問題。

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

雖然在<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類庫

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM