這篇文章寫於2002年4月,由於這個對象最終會變得越來越流行,我已經決定修改並更新這篇文章。2002版仍然可以在線閱讀,同樣的還有2004年9月版和2005年8月版。你正在閱讀的是2006年1月版。
在Windows上的Internet Explorer,Mac OS-X上的Safari,跨平台的Mozilla,KDE上的Konqueror,Java寫的IceBrowser,以及另一個跨平台的Opera(包括SymBian)都為客戶端的Javascript腳本提供了一個方法來產生HTTP請求。卑微的出身和少許推崇者的古怪命名,最終成長為在某些領域裡的核心技術,它叫作“AJAX”(欣賞一下原文:From the humble begins as an oddly named object with few admirers, it's blossomed to be the core technology in something called AJax)。 [腳注1].
這個對象讓許多事情變得比原來優雅、簡潔,並引入了一些在其他方面也一樣重要的東西,比如HEAD請求來看一個資源的最後修改時間,或者只是看它是否存在。它讓你的編碼(scripting?)的選擇更加靈活,允許POST提交,比如PUT、Delete等等操作的可能性而不需要頁面的改變。這些方法日益廣泛地應用於創建類似G-Mail的更強大提供更迅捷(snappIEr)用戶界面卻使用更少帶寬的web應用程序。
為什麼叫做“XML”HTTP Request對象?雖然這個對象被叫做“XML HTTP Request對象”,但它並不只局限於使用XML,它能夠請求或發送任何類型的文檔,雖然對於Javascript來說,處理二進制流很成問題。
創建對象
在Internet Explorer裡,你通過new ActiveXObject("Msxml2.XMLHTTP")
或new ActiveXObject("Microsoft.XMLHTTP")(
取決於安裝的MSXML版本)來創建這個對象。在Mozilla和Sarafi中(以及未來支持這個對象的類似它們倆的浏覽器(UA,User Agent)),你使用new XMLHttpRequest()
,而IceBrowser使用另外一個方法:window.createRequest()
。
這意味著你需要在不同的浏覽器上展現不同的腳本(子烏注:小小抱怨一句,我還是覺得這點小小麻煩比起css的兼容來說,簡單多了。。。。這只是個人看法,也許過幾個月,我熟悉了CSS,就不會發這種牢騷了~),在一個浏覽器上能夠干得好好的代碼,在另外一個上面八成會出錯。下面的腳本解決了這個問題,而且如果它不支持,變量XMLhttp將被設置為false並允許恰當的錯誤信息,並在這個對象不可用的時候降低要求(degradtion),使用其他普通的http傳輸方法。留有後路是很重要的,即使在ie中,這個對象也經常由於安全設置的略微修改(通常是由於IE的入侵漏洞造成的)而被禁止。當你真的無法實現功能的時候而需要提供低等級的支持時,底下是一些方法。我建議是提供另一個變通的頁面。比如GMail許諾說他們在未來(子烏注:呃。。。這文章是2002年寫的,那時候gmail還沒實現該功能)會提供一個更安全的版本,很有可能是完全沒有Javascript--完全的退化。
var XMLhttp=false;
/*@cc_on @*/
/*@if (@_JScript_version >= 5)
// JScript提供的條件編譯讓我們能夠應付舊的IE版本,以及由於安全原因無法創建對象
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (E) {
XMLhttp = false;
}
}
@end @*/
if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
try {
xmlhttp = new XMLHttpRequest();
} catch (e) {
XMLhttp=false;
}
}
if (!XMLhttp && window.createRequest) {
try {
XMLhttp = window.createRequest();
} catch (e) {
XMLhttp=false;
}
}
我如何提出一個請求?
提出一個HTTP請求非常簡單。你告訴XML HTTP request對象你需要提出什麼類型的HTTP請求以及你要請求的URL。提供一個函數給它在請求完成的時候調用,以及最後,你需要在這個請求的主體中發送的內容(如果有的話)。
下面的腳本創建了一個針對相對鏈接(相對於請求頁面)“text.txt”的GET請求。它提供了一個可供調用的函數,當readyState屬性每次變化的時候它都將被調用,而當該屬性值成為4的時候--意味著加載完成,它使用一個alert來顯示responseText給用戶。
xmlhttp.open("GET", "test.txt",true);xmlhttp.onreadystatechange=function() {if (xmlhttp.readyState==4) {alert(xmlhttp.responseText)}}XMLhttp.send(null)
提出一個HEAD請求
子烏注:我會將header翻譯成報頭,HEAD保持原文,resource翻譯成資源。個人感覺報頭比較能體現header的原義。……嗯?你不知道報頭是啥??這個不是報紙頭。如果你了解底層消息的定義,你就會知道,一條消息,不管是email,或者是短信,都會至少有兩個部分:報頭和報文,報頭中包含著這條消息的信息,而報文則是正文。這是大概解釋,還是不清楚的話就去google吧……
對於一個HEAD請求,服務器指揮返回指定資源的報頭,而不包括資源本身,這意味著你能夠在不下載一個文檔的情況下得知該文檔的Content-Type或者Last-ModifIEd。
一個典型的HEAD請求也許會返回類似底下的內容:
HTTP/1.1 200 OK
Server: Microsoft-IIS/4.0
Cache-Control: max-age=172800
Expires: Sat, 06 Apr 2002 11:34:01 GMT
Date: Thu, 04 Apr 2002 11:34:01 GMT
Content-Type: text/Html
Accept-Ranges: bytes
Last-ModifIEd: Thu, 14 Mar 2002 12:06:30 GMT
ETag: "0a7ccac50cbc11:1aad"
Content-Length: 52282
要提出HEAD請求,你只需要簡單將第一個參數替換成HEAD,然後就可以使用getAllResponseHeaders獲取報頭或者使用getResponseHeader("Name")取得單獨的報頭。
XMLhttp.open("HEAD", "/faq/index.Html",true);xmlhttp.onreadystatechange=function() {if (xmlhttp.readyState==4) {alert(xmlhttp.getAllResponseHeaders())}}XMLhttp.send(null)
使用HEAD請求,找到另一個文件的最後修改時間。
HEAD請求的用途之一,就是獲取某個url的修改時間,將之前的例子擴展一下,你將得到如下代碼:
XMLhttp.open("HEAD", "/faq/index.Html",true);xmlhttp.onreadystatechange=function() {if (xmlhttp.readyState==4) {alert("文件最後修改時間 : "+xmlhttp.getResponseHeader("Last-ModifIEd"))}}XMLhttp.send(null)
一個URL是否存在?
另一個簡單用途是判斷URL是否存在,在HTTP中,HEAD和GET請求會返回集中狀態代碼,200代表成功,404代表失敗,還有一些代表別的含義。查看HTTP狀態代碼獲取詳細的解釋。XMLhttp對象使用status屬性來告訴你這個狀態。
XMLhttp.open("HEAD", "/faq/index.Html",true);xmlhttp.onreadystatechange=function() {if (xmlhttp.readyState==4) {if (xmlhttp.status==200) alert("URL 存在!")else if (xmlhttp.status==404) alert("URL 不存在!")else alert("狀態是 "+xmlhttp.status)}}XMLhttp.send(null)
無刷新調用服務端腳本
在Html中,表單是一種調用服務端腳本的方法,它們會強制頁面刷新,而這對用戶來說通常並不是非常的友好。使用Http Request,你能夠在不刷新頁面的前提下調用腳本,並且在XML HTTP Request對象不可用的時候還能退而使用表單。
<%a=+(Request.QueryString('a')+'')b=+(Request.QueryString('b')+'')if (isNaN(a) || isNaN(b)) {a='';b='';total='' }else {total=a+b}acc=Request.ServerVariables('HTTP_ACCEPT')+''if (acc.indexOf('message/x-jl-formresult')!=-1) {Response.Write(total)} else {%><script src="XMLhttp.JS" type="text/Javascript"></script><script>function calc() {frm=document.forms[0]url="add.1?a="+frm.elements['a'].value+"&b="+frm.elements['b'].valuexmlhttp.open("GET",url,true);xmlhttp.onreadystatechange=function() {if (xmlhttp.readyState==4) {document.forms[0].elements['total'].value=xmlhttp.responseText}}xmlhttp.setRequestHeader('Accept','message/x-jl-formresult')XMLhttp.send()return false}</script><form action="add.1" method="get" ><input type=text name=a value="<%=a%>"> + <input type=text name=b value="<%=b%>">= <input type=text name=total value="<%=total%>"><input type=submit value="Calculate"></form><%}%>
上面的示例使用了ASP的JScript作為服務端語言,HTTP ACCEPT報頭被用來告訴服務端需要發送回什麼樣的響應--完整的頁面或者只是結果。這個HTTP ACCEPT報頭用來告訴服務端客戶端會接受什麼樣的mime-types,通常它會是一些類似 text/Html
的內容。這裡我們告訴它我們只接受message/x-jl-formresult
,所以服務器知道它是我們的客戶端(或者其他知道message/x-jl-formresult
的客戶端)提出的請求。
另一個判斷返回內容的方法是通過你發送給服務器的數據的類型推斷,你也可以簡單將表單提交給xmlhttp request的url變換一下。無論你怎麼作,保留對可能存在的不支持XMLhttp request的浏覽器的向下兼容是很明智的。
使用JSON作為傳輸語言
雖然XML能夠被用於編碼(encode)你用這個對象取回的數據,並通過responseXML來訪問數據,但是XML還是不能被很好的支持,一些浏覽器要求資源的內容類型必須是兩種可能的XML mime-type之一:text/XML
或application/XML
才能夠被接受,而且你處理的XML永遠可能會出現格式良好問題。JSON是一個很好的替換技術,它解析速度很快,而且,在腳本中訪問,速度快得多得多。
我在我的Flight Routeplanner中使用了JSON來查找航班的信息,比如London Heathrow,你能夠輕易的將返回的JSON使用new Function
初始函數轉換成一個腳本對象,這個對象將檢查狀態,如果從iata代碼中查找航班失敗的話,將返回一個404給腳本(it checks the status as the script returns 404 if it fails to find an airport with that iata code)。
xmlhttp.open("GET","/routeplanner/airport.1?LHR",true);xmlhttp.onreadystatechange=function() {if (xmlhttp.readyState==4) {if (xmlhttp.status!=404) {var local=new Function("return "+xmlhttp.responseText)();alert("Code - Name\n"+local[0].id+' - '+local[0].name);} else {alert("Airport not found");}}}XMLhttp.send(null);
使用XMLHTTP操作GOOGLE的SOAP API
Google針對它的數據庫提供了一個SOAP接口。你為了發出請求,需要注冊一個可以每天使用1000次的key。然後你需要自己解析返回的XML文件。
search="Word"xmlhttp.open("POST", "http://api.google.com/search/beta2",true);xmlhttp.onreadystatechange=function() {if (xmlhttp.readyState==4) {alert(xmlhttp.responseText)}}xmlhttp.setRequestHeader("Man", "POST http://api.google.com/search/beta2 HTTP/1.1")xmlhttp.setRequestHeader("MessageType", "CALL")xmlhttp.setRequestHeader("Content-Type", "text/xml")xmlhttp.send("<?xml version='1.0' encoding='UTF-8'?>"+"\n\n"+"<SOAP-ENV:Envelope"+' xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"'+' xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"'+' xmlns:xsd="http://www.w3.org/1999/XMLSchema">'+'<SOAP-ENV:Body><ns1:doGoogleSearch'+' xmlns:ns1="urn:GoogleSearch"'+' SOAP-ENV:encodingStyle="http://schemas.XMLsoap.org/soap/encoding/">'+'<key xsi:type="xsd:string">GOOGLEKEY</key> <q'+' xsi:type="xsd:string">'+search+'</q> <start'+' xsi:type="xsd:int">0</start> <maxResults'+' xsi:type="xsd:int">10</maxResults> <filter'+' xsi:type="xsd:boolean">true</filter> <restrict'+' xsi:type="xsd:string"></restrict> <safeSearch'+' xsi:type="xsd:boolean">false</safeSearch> <lr'+' xsi:type="xsd:string"></lr> <ie'+' xsi:type="xsd:string">latin1</IE> <oe'+' xsi:type="xsd:string">latin1</oe>'+'</ns1:doGoogleSearch>'+'</SOAP-ENV:Body></SOAP-ENV:Envelope>')
Google使用的是SOAP接口,許多人認為SOAP存在需要值得慎重考慮的問題。而REST也許是一個更好的模型,因為它能夠與當前的web框架、代理、緩存等等進行協作。所以雖然我們能夠使用XML HTTP Request與soap通信,在我們確實沒法控制服務端所所發生的一切之前還是盡量不要使用它。(感謝Dan SchmIErer指出了我腳本中的一個錯誤。)
默認情況下這個對象只能回調同一台服務器,而在一個安全要求降低的環境下(指通過file://訪問),IE能夠訪問任何的域,而Mozilla也那能夠實現,如果你提出請求,就會獲取適當的權限。(子烏注:下面這句我不會翻譯。。。啥意思?"a google thread I can't get to offline!")
腳注1:實際上許多“AJax”應用程序幾乎不使用這個XML HTTP REQUEST對象,而是使用更老、但在大多數情況下更靈活的IFRAME執行遠程腳本方法,但他們(“AJax”應用程序)也能夠使用這個對象,而且應該養成AJax是一種使用XML HTTP REQUEST創建的應用程序的觀念。