[翻译]使用 XML HTTP Request 对象(2006.1)
作者:Jim Ley(主页)
译者:Sheneyan(子乌)
时间:2006.1.29
英文原文:http://jibbering.com/2002/4/httprequest.html
子乌注:这篇文章我想看过的人很多,翻译的版本也有许多,我之所以要再来翻译一次,是因为这篇文章的时效性(看下一段作者说明),以及文字的优雅。文字的时效性,它居然是2006年1月再次更改的版本,也就是说,我也许就是第一个翻译这个版本的人哦:P。文字的优雅,有能力、有空还是得看看原文,那英文的美感,在类似的技术文章中算很少见的(也许是我少见多怪,等我看多了就不会这样了~)
这篇文章写于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--完全的退化。
|
1 |
var
xmlhttp=
false
;
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
if
(!xmlhttp &&
typeof
XMLHttpRequest!=
'undefined'
) {
|
16 |
try { |
17 |
xmlhttp = new XMLHttpRequest(); |
18 |
} catch (e) { |
19 |
xmlhttp=false; |
20 |
} |
21 |
} |
22 |
if
(!xmlhttp && window.createRequest) {
|
23 |
try { |
24 |
xmlhttp = window.createRequest(); |
25 |
} catch (e) { |
26 |
xmlhttp=false; |
27 |
} |
28 |
} |
|
view plain
| print | copy to clipboard | ? |
我如何提出一个请求?
提出一个HTTP请求非常简单。你告诉XML HTTP request对象你需要提出什么类型的HTTP请求以及你要请求的URL。提供一个函数给它在请求完成的时候调用,以及最后,你需要在这个请求的主体中发送的内容(如果有的话)。
下面的脚本创建了一个针对相对链接(相对于请求页面)“text.txt”的GET请求。它提供了一个可供调用的函数,当readyState属性每次变化的时候它都将被调用,而当该属性值成为4的时候--意味着加载完成,它使用一个alert来显示responseText给用户。
|
1 |
xmlhttp.open(
"GET"
,
"test.txt"
,
true
);
|
2 |
xmlhttp.onreadystatechange=function() { |
3 |
if (xmlhttp.readyState==4) { |
4 |
alert(xmlhttp.responseText) |
5 |
} |
6 |
} |
7 |
xmlhttp.send(null) |
|
view plain
| print | copy to clipboard | ? |
(示例看这里)
提出一个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")取得单独的报头。
|
1 |
xmlhttp.open(
"HEAD"
,
"/faq/index.html"
,
true
);
|
2 |
xmlhttp.onreadystatechange=function() { |
3 |
if (xmlhttp.readyState==4) { |
4 |
alert(xmlhttp.getAllResponseHeaders()) |
5 |
} |
6 |
} |
7 |
xmlhttp.send(null) |
|
view plain
| print | copy to clipboard | ? |
(示例看这里)
使用HEAD请求,找到另一个文件的最后修改时间。
HEAD请求的用途之一,就是获取某个url的修改时间,将之前的例子扩展一下,你将得到如下代码:
|
1 |
xmlhttp.open(
"HEAD"
,
"/faq/index.html"
,
true
);
|
2 |
xmlhttp.onreadystatechange=function() { |
3 |
if (xmlhttp.readyState==4) { |
4 |
alert("文件最后修改时间 : "+ |
5 |
xmlhttp.getResponseHeader("Last-Modified")) |
6 |
} |
7 |
} |
8 |
xmlhttp.send(null) |
|
view plain
| print | copy to clipboard | ? |
(示例看这里)
一个URL是否存在?
另一个简单用途是判断URL是否存在,在HTTP中,HEAD和GET请求会返回集中状态代码,200代表成功,404代表失败,还有一些代表别的含义。查看HTTP状态代码获取详细的解释。xmlhttp对象使用status属性来告诉你这个状态。
|
1 |
xmlhttp.open(
"HEAD"
,
"/faq/index.html"
,
true
);
|
2 |
xmlhttp.onreadystatechange=function() { |
3 |
if (xmlhttp.readyState==4) { |
4 |
if (xmlhttp.status==200) alert("URL 存在!") |
5 |
else if (xmlhttp.status==404) alert("URL 不存在!") |
6 |
else alert("状态是 "+xmlhttp.status) |
7 |
} |
8 |
} |
9 |
xmlhttp.send(null) |
|
view plain
| print | copy to clipboard | ? |
(存在的URL不存在的URL )
无刷新调用服务端脚本
在HTML中,表单是一种调用服务端脚本的方法,它们会强制页面刷新,而这对用户来说通常并不是非常的友好。使用Http Request,你能够在不刷新页面的前提下调用脚本,并且在XML HTTP Request对象不可用的时候还能退而使用表单。
|
1 |
<%
|
2 |
a=+(Request.QueryString('a')+'') |
3 |
b=+(Request.QueryString('b')+'') |
4 |
if (isNaN(a) || isNaN(b)) {a='';b='';total='' } |
5 |
else { |
6 |
total=a+b |
7 |
} |
8 |
acc=Request.ServerVariables('HTTP_ACCEPT')+'' |
9 |
if (acc.indexOf('message/x-jl-formresult')!=-1) { |
10 |
Response.Write(total) |
11 |
} else { |
12 |
%> |
13 |
<script src="xmlhttp.js" type="text/javascript"></script> |
14 |
<script> |
15 |
function calc() { |
16 |
frm=document.forms[0] |
17 |
url="add.1?a="+frm.elements['a'].value+"&b="+frm.elements['b'].value |
18 |
xmlhttp.open("GET",url,true); |
19 |
xmlhttp.onreadystatechange=function() { |
20 |
if (xmlhttp.readyState==4) { |
21 |
document.forms[0].elements['total'].value=xmlhttp.responseText |
22 |
} |
23 |
} |
24 |
xmlhttp.setRequestHeader('Accept','message/x-jl-formresult') |
25 |
xmlhttp.send() |
26 |
return false |
27 |
} |
28 |
</script> |
29 |
<form action="add.1" method="get" onsubmit="return calc()"> |
30 |
<input type=text name=a value="<%=a%>"> + <input type=text name=b value="<%=b%>"> |
31 |
= <input type=text name=total value="<%=total%>"> |
32 |
<input type=submit value="Calculate"> |
33 |
</form> |
34 |
<% |
35 |
} |
36 |
%> |
|
view plain
| print | copy to clipboard | ? |
(示例页面看这里)
上面的示例使用了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)。
|
1 |
xmlhttp.open(
"GET"
,
"/routeplanner/airport.1?LHR"
,
true
);
|
2 |
xmlhttp.onreadystatechange=function() { |
3 |
if (xmlhttp.readyState==4) { |
4 |
if (xmlhttp.status!=404) { |
5 |
var local=new Function("return "+xmlhttp.responseText)(); |
6 |
alert("Code - Name\n"+local[0].id+' - '+local[0].name); |
7 |
} else { |
8 |
alert("Airport not found"); |
9 |
} |
10 |
} |
11 |
} |
12 |
xmlhttp.send(null); |
|
view plain
| print | copy to clipboard | ? |
(没现成代码,暂不演示)
使用XMLHTTP操作GOOGLE的SOAP API
Google针对它的数据库提供了一个SOAP接口。你为了发出请求,需要注册一个可以每天使用1000次的key。然后你需要自己解析返回的XML文件。
|
1 |
search=
"Word"
|
2 |
xmlhttp.open("POST", "http://api.google.com/search/beta2",true); |
3 |
xmlhttp.onreadystatechange=function() { |
4 |
if (xmlhttp.readyState==4) { |
5 |
alert(xmlhttp.responseText) |
6 |
} |
7 |
} |
8 |
xmlhttp.setRequestHeader("Man", "POST http://api.google.com/search/beta2 HTTP/1.1") |
9 |
xmlhttp.setRequestHeader("MessageType", "CALL") |
10 |
xmlhttp.setRequestHeader("Content-Type", "text/xml") |
11 |
|
12 |
xmlhttp.send("<?xml version='1.0' encoding='UTF-8'?>"+"\n\n"+"<SOAP-ENV:Envelope"+ |
13 |
' xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"'+ |
14 |
' xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"'+ |
15 |
' xmlns:xsd="http://www.w3.org/1999/XMLSchema">'+ |
16 |
'<SOAP-ENV:Body><ns1:doGoogleSearch'+ |
17 |
' xmlns:ns1="urn:GoogleSearch"'+ |
18 |
' SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'+ |
19 |
'<key xsi:type="xsd:string">GOOGLEKEY</key> <q'+ |
20 |
' xsi:type="xsd:string">'+search+'</q> <start'+ |
21 |
' xsi:type="xsd:int">0</start> <maxResults'+ |
22 |
' xsi:type="xsd:int">10</maxResults> <filter'+ |
23 |
' xsi:type="xsd:boolean">true</filter> <restrict'+ |
24 |
' xsi:type="xsd:string"></restrict> <safeSearch'+ |
25 |
' xsi:type="xsd:boolean">false</safeSearch> <lr'+ |
26 |
' xsi:type="xsd:string"></lr> <ie'+ |
27 |
' xsi:type="xsd:string">latin1</ie> <oe'+ |
28 |
' xsi:type="xsd:string">latin1</oe>'+ |
29 |
'</ns1:doGoogleSearch>'+ |
30 |
'</SOAP-ENV:Body></SOAP-ENV:Envelope>') |
|
view plain
| print | copy to clipboard | ? |
Google使用的是SOAP接口,许多人认为SOAP存在需要值得慎重考虑的问题。而REST也许是一个更好的模型,因为它能够与当前的web框架、代理、缓存等等进行协作。所以虽然我们能够使用XML HTTP Request与soap通信,在我们确实没法控制服务端所所发生的一切之前还是尽量不要使用它。(感谢Dan Schmierer指出了我脚本中的一个错误。)
默认情况下这个对象只能回调同一台服务器,而在一个安全要求降低的环境下(指通过file://访问),IE能够访问任何的域,而Mozilla也那能够实现,如果你提出请求,就会获取适当的权限。(子乌注:下面这句我不会翻译。。。啥意思?"a google thread I can't get to offline!")
posted on 2006-03-22 09:33
汪杰 阅读(691)
评论(0) 编辑 收藏 引用