超文本傳輸協議(HTTP)規定web瀏覽器如何從web服務器獲取文檔和向web服務器發送表單內容,以及web服務器如何響應這些請求和提交。web瀏覽器會處理大量的HTTP。一般,HTTP並不在腳本的控制下,只是當用戶單擊連接、提交表單和輸入URL時才發送。javascript
可是,用javascript代碼操縱HTTP是可行的。當腳本設置window對象的location屬性或調用表單對象的submit()方法時,都會初始化HTTP請求。在這種狀況下,瀏覽器會重新加載頁面。但這章討論在沒有致使web瀏覽器從新加載任何窗口或窗體內容的狀況下,腳本如何實現瀏覽器與服務器之間的通訊。php
術語Ajax描述了一種主要使用腳本操做HTTP的web應用構架。(Ajax是Asynchronous javascript and XML的縮寫,這個術語由jesse James Carrett創造,最先出現於2005年2月它發佈的文章。)。Ajax應用的主要特色是使用腳本操做HTTP和web服務器進行數據交換,不會致使頁面重載。避免頁面重載(這是web初期的標準作法)的能力能使web應用感受更像傳統的桌面應用。web可使用Ajax技術把用戶的交互記錄數據記錄到服務器中;也能夠是簡單的顯示頁面,以後按需加載額外的數據和頁面組件來提示應用的啓動時間。css
Comet是和使用腳本操做HTTP的web應用構架相關的術語(Comet這個名字是Alex Russell在2006年3月創造)。在某種意義上,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)當網頁信標不是與當前網頁服務器而是其它服務器交流信息時,會擔憂隱私泄露。這種第三方的網頁信標方式經常使用於統計點擊數和網站流量分析)。web
<img>元素沒法實現完整的的Ajax傳輸協議,由於數據交換是單向的:客戶端能發送數據到服務器,但服務器的響應一直是張圖片致使客戶端沒法從中獲取提取信息。然而<iframe>元素更增強大,爲了把<iframe>做爲Ajax傳輸協議使用,腳本首先要把發送給web服務器的信息編碼到URL中,而後設置<iframe>的src屬性爲該URL。服務器建立一個包含響應內容的HTML文檔。並把它返回給web瀏覽器。而且在<iframe>中顯示它。<iframe>須要對用戶不可見。可使用css隱藏它。腳本能遍歷<iframe>的文檔對象來讀取服務端的響應,注意,這種方法受限於13.6.2介紹的同源策略問題。ajax
實際上,<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>有規避同源限制的能力。json
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更精妙,但須要客戶端和服務器創建(必要時從新創建)鏈接。同時要保存服務器鏈接處於打開狀態。這樣它才能發送異步信息。隱藏的<iframe>能像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,遠程過程調用)機制或發佈/訂閱事件系統基礎。
瀏覽器在XMLHttpRequest類上定義了它們的HTTP API。這個類的每一個實例都表示一個獨立的請求/響應對。而且這個對象的屬性和方法容許指定請求細節和提取響應數據。
使用這個HTTP API作的第一件事就是實例化XMLHttpRequest對象。
你也能夠重用已存在的XMLHttpRequest,但注意這將會終止以前經過該對象掛起的任何請求。
IE6中的XMLHttpRequest
微軟最先把XMLHttpRequest對象引入到IE5中,且在IE5和6中它只是一個ActiveX對象。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請求響應包含3個部分:
接下來兩節咱們將會展現如何設置HTTP請求的每一個部分和如何查詢HTTP響應的每一個部分。
HTTP的基礎請求/響應構架很是簡單並易用使用。但在實踐中會有各類各樣的帶來複雜的問題:客戶端和服務器交換cookie,服務器重定向瀏覽器到其它服務器,緩存某些資源而剩下的不緩存,某些客戶端經過代理服務器發送全部的請求等。XMLHttpRequest不是協議級的HTTP API而是瀏覽器級的API,瀏覽器須要考慮cookie、重定向、緩存和代理,但代碼只須擔憂請求和響應。
測試的時候須要上傳到服務器或者使用本地服務器。XMLHttpRequest用於同HTTP和HTTPS協議一塊兒工做,不少同喜只有經過HTTP纔有的。
open()方法
你能爲請求指定「Authorization」頭,但一般不須要這麼作。若是請求一個受密碼保護的URL,把用戶名和密碼做爲第4個和第5個參數傳遞給open(),則XMLHttpRequest將設置合適的頭。(接下來了解open()可選的第三個參數。可選的用戶名和密碼參數會在第4部分介紹)。
send()方法
順序問題
HTTP請求的各部分有指定順序:請求方法和URL首先到達,而後是請求頭,最後是請求主體。XMLHttpRequest實現一般直到調用send()方法開始啓動網絡。但XMLHttpRequest API的設計彷佛使每一個方法都寫入網絡流。這意味這調用XMLHttpRequest方法的順序必須匹配HTTP請求的構架。例如setRequestHeader()方法的調用必須在調用open()以前但在調用send()以後。不然它將拋出異常。
它用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); //將msg做爲請求主體發生 //請求完成 ,將忽略任何響應和錯誤 }
上個例子的send()方法啓動請求,而後返回。當它等待的服務器的響應時間並不阻塞。
使用getResponseHeader()和getAllResponseHeaders()能查詢響應頭。XMLHttpRequest會自動處理cookie:它會從getAllResponseHeaders()頭返回集合中中過濾掉的cookie頭,而若是給getResponseHeader()傳遞「Set-Cookie」和「Set-cookie2」則返回true。
XMLHttpRequest對象一般(除了見下面的「同步響應」節的內容)異步使用:發送請求後,send()方法當即返回,直到響應返回,前面列出的響應方法和屬性纔有效。爲了響應準備就緒時獲得通訊,必須監聽XMLHttpRequest對象上的readystatechange事件(或者4小節描述的XHR進度事件)。但爲了理解這個事件類型,你必須理解readyState屬性。
下面的例子定義了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); //當即發送請求 }
(1).同步響應
比較上例子的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凍結。若是鏈接的服務器響應慢,那麼用戶的瀏覽器凍結。然而,22.4節能夠接受使用同步的請求的場景。
(2)響應解碼
若是服務器想發送諸如對象或數組這樣的結構化數據做爲其響應,它應該傳輸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的腳本的能力徹底能夠實現加載並執行腳本。見例13-4,且記住<script>元素能發起跨域HTTP請求,而XMLHttpRequest API則禁止。
web服務端一般使用二進制數據(好比圖片文件)響應HTTP請求,responseText屬性只能用於文本,且它不能妥善處理二進制響應,即便對最終字符串使用了charCodeAt()方法,詳情參見22.6.2節。
服務器響應的正常解碼是假設服務器爲這個響應發送了"Content-Type"頭和正確的MIME類型。例如,若是服務器發送了XML文檔但沒有設置適當的MIME類型,那麼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")
HTTP POST請求包含一個請求主體,它包含客戶端傳遞給服務器的數據。在例18-1中,請求主體是簡單的文本字符串,可是咱們一般使用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://restaurantfinder.example.com/02134/1km/pizza
⑵JSON編碼的請求
/**使用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)); }
(3)XML編碼的請求
javascript對象的用表單編碼或JSON編碼表達的pizza查詢,也能用XML文檔來表示它。例如,以下所示:
<query>
<find zipcode="02134" radius="1km">
pizza
</find>
</query>
在以前的示例中,使用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事件。(18.1.5節包含超時和abort方法的內容。)最後,像太多重定向這樣網絡錯誤會阻止請求完成,但這些狀況發生時會觸發error事件。
對應任何具體請求,瀏覽器將只會觸發load、abort、timeout、error事件中的一個。XHR2規範草案指出一旦這些事件中的一個發生後,瀏覽器會觸發loadend事件。
能夠經過XMLHttpRequest對象的addEventListener()方法爲這些progress事件中的每一個都註冊處理程序。若是每種事件只有一個事件處理程序,一般更容易的方法是隻設置對應的處理程序屬性,好比onprogress和onload。甚至可使用這些事件屬性是否來存在來測試瀏覽器是否支持progress事件:
if("onprogress" in (new XMLHttpRequest())){ console.log("good!") //支持progress事件 }
除了像type和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請求中上傳多個文件。
本章概述提到過<script>元素能夠做爲一種ajax傳輸機制:只需設置<script>元素的src屬性(假如它還沒插入到document中,須要插入進去),而後瀏覽器就會發送一個HTTP請求如下載src屬性所指向的url。使用<script>元素進行ajax傳輸的一個主要緣由是,它不受同源策略的影響,所以可使用它們從其它的服務器請求數據。第二個緣由是包含JSON編碼數據的響應體會自動解碼(即,執行)。
腳本和安全
爲了使用<script>元素進行AJAX傳輸,必須容許web頁面可執行遠程服務器發送過來的任何javascript代碼。這意味着對不可信的服務器,不該該採起此措施。當與可信的服務器通訊時,要堤防攻擊者可能進入服務器中,而後黑客會接管你的網頁,運行他本身的代碼,並顯示任何他想要的內容,這表現得就像這些內容本就來自你的網站。
假設你已經寫過一個服務,它處理GET請求並返回JSON編碼的數據。同源的文檔能夠在代碼中使用XMLHttpRequest和JSON.parse()。假如在服務器上啓用了CORS,跨域文檔也可因經過XMR享受到該服務。不支持的話就用JSONP,JSON響應數據(理論上)是合法的JavaScript代碼,當它到達時瀏覽器將執行它。
這就是JSONP中P的意義所在。當經過<script>元素調用數據是,響應內容必須用javascript函數名和圓括號包裹起來。而不是發送這樣的一段JSON數據:
[1,2{"bukle":"my shoes"}]
它會發送這樣的一個包裹後的JSON響應:
handleResponse( [1, 2 {"bukle": "my shoes"}] )
包裹後的響應會成爲<script>元素的內容,它先判斷JSON編碼後的數據(畢竟就是一個javascript表達式),而後把它傳遞給handleResponse()函數,咱們能夠假設文檔拿這些數據會作一些有用的事情。
爲了可行起見,咱們必須經過某種方式告訴服務,它正在從一個<javascript>元素調用,必須返回JSONP響應,而不該該是普通的JSON響應。這個能夠經過URL中添加一個參數來實現,例如:追加「?json」(或&json)。
在實踐中,支持JSONP的服務不會強制指定客戶端必須實現的回調函數名稱,好比handleResponse。相反,他們使用查詢參數的值,容許客戶端指定一個函數名,而後使用函數名去填充響應。下面的例子使用了一個名爲jsonp的查詢參數來指定回調函數的名稱。許多支持JSONP的服務都能分辨出這個參數名。另一個常見的參數名稱是callback,爲了讓使用到的服務支持相似特殊的需求,就須要在代碼上作一些修改了。
下面的例子定義了一個getJSOP()函數,它發送JSONP請求。這個例子有點複雜,有幾點值得注意。首先,注意它是如何建立一個新的<Script>元素,設置其URL,並將它插入到文檔中。正是插入操做觸發HTTP請求。其次,注意下面的的例子爲每一個請求都建立了一個全新的內部回調函數,回調函數做爲getJSONP()函數的一個屬性存儲起來。最後要注意的是回調函數作了一些清理工做:刪除腳本元素,並刪除自身。