XMLHttpRequest调用
XMLHttpRequest Call
●●●
调用,回调,下载,抓取,实时,查询,远程通信(Remoting),远程通信脚本(RemoteScripting),同步,上传,XMLHttpRequest
图6-2:XMLHttpRequest调用
目标故事
Reta正在一个批发商网站上购买商品。每次她添加一个商品到购物车时,web站点发出 XMLHttpRequest以储存最新的购物车内容。没有表单提交,这些商品被实时增加,这种方式帮助 Reta 节省了时间,并且有助于了解正在购物的过程。
问题
浏览器怎样与服务器通信?
先决条件
l Ajax应用要求浏览器——服务器的通信方式。用户生成的信息必须被上传,新的服务器信息必须被下载。
l 因为Ajax应用应该有平顺和连续的感觉,浏览器——服务器的通信方式必须是不唐突的。
l Ajax应用应该具有高度响应性,因此调用应涉及最小的数据传输。
l 因为网络经常是不可靠的,性能也不稳定,调用应该是异步的,让用户在网络调用进行时继续工作。
方案
采用XMLHttpRequest对象用于浏览器——服务器之间的通信。JavaScript对于通用的网络通讯缺乏一种可 移植的机制,这种限制基于安全理由并无不妥,并且或许将会继续存在。但是由于XMLHttpRequest对象对于当前所有的主流浏览器都是支持的, JavaScript代码能够产生HTTP回调,发送到它的源服务器(下载主机),并且取得结果。通过这种方式,你能够产生细粒度的服务器调用,并且正如 你所愿地处理响应。与传统的表单提交不同,传统的表单提交会引起整个页面的刷新。注意:在线演示应用(http://ajaxify.com/run/ xmlHttpRequestCall)说明了整个方案里的程序代码概念和程序代码片断。
这种模式使用了上文在Web服务所提到加法运算的Web服务,具有像 http://ajaxify. com/run/xmlHttpRequestCall/sumGet.phtml?figure1=5&figure2=10那样的URL。在这个案例中,返回了两数之和“15”。你可以通过在浏览器内输入完整的URL测试一下,但在这里,我们想要通过JavaScript调用它,并且捕获结果。以下是一个非常基本的例子:
var xhReq = new XMLHttpRequest();
xhReq.open("GET", "sumGet.phtml?figure1=5&figure2=10", false);
xhReq.send(null);
var serverResponse = xhReq.responseText;
alert(serverResponse); // 显示 "15"
首先,从建立一个XMLHttpRequest的新实例开始,接着,xhReq.open()准备一个对测试服务 “sumGet.phtml”的调用(代码从相同的路径执行,因此域和路径没有问题)。GET表示要使用的请求方法,false参数说明调用是同步的,即 代码将阻塞,直到返回响应。send发出完成请求的指令。因为这个调用是同步的,下一行代码一旦被执行,则表示调用结果已经完成, XMLHttpRequest对象已经储存了从服务器获取的响应,你可以使用responseText字段访问响应内容。
上述范例显示这种基础技术相当简单。不过,要知道,这只是基本的使用,还不适合用在产品上。基本的问题依然存在,我们将在整个解决方案中得到答案:
l 怎样取得XMLHttpRequest?
l 异步调用怎样工作?
l 怎样处理错误?
l 万一服务要求POST或PUT请求,而不是GET,那该怎么办?
l 若是外部域的情况,会有什么限制呢?
l 怎样处理XML响应?
l API是什么?
当你读到上述全部时,要知道有很多很多代码库都支持远程通信(remoting,见附录A)。大多数开发者不需要直接接触 XMLHttpRequest。也就是说,了解XMLHttpRequest调用以及其他Web远程通信技术的能力和限制是件好事。这项知识有助于你选择 最合适的代码库,并且帮你处理任何可能遇到的缺陷(bug)。
建立XMLHttpRequest对象
在大多数浏览器里,XMLHttpRequest是标准的JavaScript类(class),你刚刚创建了 XMLHttpRequest的新实例。不过,Microsoft是XMLHttpRequest的发明者,但是在IE7 之前,IE 只提供 ActiveX 对象。更复杂的是,这个对象存在不同的版本。下面的代码显示一个工厂函数(factory function),可以在任何支持XMLHttpRequest的浏览器上运行:
function createXMLHttpRequest() {
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
try { return new XMLHttpRequest(); } catch(e) {}
alert("XMLHttpRequest not supported");
return null;
}
...
var xhReq = createXMLHttpRequest();
你的确需要使用像这样的函数,以达成最大的可移植性。一旦有了这个对象,它的基本功能和API在各浏览器上是相当一致的,但是请务必仔细测试,因为在某些浏览器的实现上,有一些细微的差别。(如果你好奇,本章稍后HTTP 串流的“方案”,将强调这样的不一致性。)
你也可以重用XMLHttpRequest;这么做是值得的,可以防止内存泄露。为了安全起见,只有在没有调用的前提下,才 开启一个新调用。正如下文解释的,查看一个调用的状态是可能的,只有在状态为0或4时,才会开启一个调用。因此,如果是其他状态,将会先调用abort () 方法重置状态。
异步调用
上文的“方案”中已经提到,在同步模式下,“代码的执行将阻塞,直到响应返回”,某些读者或许对这种想法感到难以理解。我们都知道某些请求需要花很长的时间来处理,而且某些根本不会返回。当服务器脚本陷于无穷循环时,我们只能对这个用户表示同情。
实际上,XMLHttpRequest调用应该几乎总是异步。这意味着,在等待响应返回时,浏览器与用户可以继续执行其他工 作。你怎样知道响应何时会准备好?XMLHttpRequest的 readyState总是会反映出目前是在调用生命周期中的哪一点。当XMLHttpRequest对象产生时,它是0。open()被调用后,则为1。 继续进行直到响应返回,在那一点上,其值为4。
因此,为捕获响应,你需要留意4的readyState。那是很容易的,因为XMLHttpRequest触发 readystatechange事件,你可以使用onreadystatechange字段声明回调函数,接着,回调函数将接收任何的状态改变。低于4 的状态不是特别有用,并且在不同的浏览器上,还有些不一致(http://www.quirksmode.org/blog/archives/2005/09/xmlhttp_notes_ r_2.html)。因此,多数时间,我们感兴趣的是,“你是在状态4(即完成)还是不在?”
基于以上的这些考虑,以下是上文显示过的程序代码的异步版本:
var xhReq = createXMLHttpRequest();
xhReq.open("GET", "sumGet.phtml?figure1=5&figure2=10", true);
xhReq.onreadystatechange = onSumResponse;
xhReq.send(null);
...
function onSumResponse() {
if (xhReq.readyState != 4) { return; }
var serverResponse = xhReq.responseText;
...
}
正如以上代码所示,你在XMLHttpRequest的onreadystatechange属性中,声明了这个回调方法。 另外,open()的第3个参数现在是true,这个参数实际上被称为“异步标志”(asynchronous flag),这说明了现在我们为什么把它设为true。回调函数,onSumResponse,使用onreadystatechange被注册,并且包 含一个保护条件子句(guard clause),确保在任何处理发生前,readyState是4。在那个时间点上,我们在response- Text里有完整的响应消息。
JavaScript 也支持“闭环”(closures)——一种匿名函数的形式——它为异步呼叫提供一种较精简的固定结构:
var xhReq = createXMLHttpRequest();
xhReq.open("get", "sumget.phtml?figure1=10&figure2=20", true);
xhReq.onreadystatechange = function() {
if (xhReq.readyState != 4) { return; }
var serverResponse = xhReq.responseText;
...
};
xhReq.send(null);
谨慎地使用闭环,因为每一次你都是在定义一个新函数。这比引用到一个现有的函数慢,而且可能导致内存泄露。
异步调用是很重要的,但也更容易出错。如果你回顾调用机制,你可能会注意到那些细微的,但是严重的潜在缺陷(bug)。当同 一个XMLHttpRequest实例同时用于不同的调用时,问题将浮出水面。当对象还在等待调用1的响应时,如果调用2被发出,回调函数将接收到什么内 容?事实上,在调用1返回前,回调函数本身甚至可能已经被改变。有一些方法可以处理这个问题,它们是“调用跟踪”(第10章)的主题。
检测错误
有时,请求并不如你预期的返回,或许根本没有返回。你的调用有错,或服务器存在缺陷,或者基础设施的某部分刚刚出了点问题。异步思考是处理这些问题的第一步,因为至少你的应用没有因此而停滞,但是你需要做的不止如此。
为检测服务器错误,你可以使用XMLHttpRequest的status标志,检查响应状态。这只是标准的HTTP响应码 (HTTP code)。例如,如果来源丢失,XMLHttpRequest.status将呈现著名的“404”值。多数情况下,你可以假定除了200以外的任何其 他值都是错误情况。这里建议为上一节的回调函数增加新的检查:
xhReq.onreadystatechange = function() {
if (xhReq.readyState != 4) { return; }
if (xhReq.status != 200) {
var serverResponse = xhReq.responseText;
...
};
如果浏览器检测到问题发生,那最好,但是有时请求将永远丢失。因此,你通常会想要有某种超时(timeout)机制(http://ajaxblog.com/archives/2005/06/01/ async-requests-over-an- unreliable-network)。建立Scheduling定时器追踪会话(session)。如果请求花费太长时间,定时器将介入,你就能处理错误。XMLHttpRequest有一个abort()函式,在超时状况下,你应该调用它。这里是范例程序代码:
var xhReq = createXMLHttpRequest();
xhReq.open("get", "infiniteLoop.phtml", true); // 服务器陷入循环里
var requestTimer = setTimeout(function() {
xhReq.abort();
$("response").innerHTML += ". *** Timed out at " + new Date();
}, 5000);
xhReq.onreadystatechange = function() {
if (xhReq.readyState != 4) { return; }
clearTimeout(requestTimer);
if (xhReq.status != 200) {
// 处理错误,例如在页面上显示错误消息
return;
}
var serverResponse = xhReq.responseText;
...
};
对比上文的例子,这里引入了一个定时器。一旦onreadystatechange回调函数接收到全部响应,它将清除定时器 (即使响应恰好错误)。在此清除动作未发生的情况下,定时器将在超时时触发,setTimeout所约定的abort()将被调用,接着将执行一些恢复的 动作。
处理POST和其他请求类型
到目前为止,请求一直是简单的GET类型——传入URL并捕获响应。正如RESTful Service(第 9章)所讨论的,真实世界的项目也需要以其他请求类型执行。例如,POST 适合会影响服务器状态或上传大量数据的调用。为了说明,我们现在建立一个新服务,sumPostGeneric.phtml,这个服务完成与 PostGet.phtml相同的功能,但是以 POST 消息执行。它被称为“通用的”(Generic),是因为它读取整个消息主体(body)文字,而不是CGI风格的表单提交。这样的话,它预期会接收到 body,例如“Calculate this sum: 5+6”,并且返回相加值:
<?
$body = readBody();
ereg("Calculate this sum: ([0-9]+)/+([0-9]+)", $body, $groups);
echo $groups[1] + $groups[2];
// PHP 方法,读取任何 POST 的 body 内容。
function readBody() {
$body="";
$putData = fopen("php://input", "r");
while ($block = fread($putData, 1024)) {
$body = $body.$block;
}
fclose($putData);
return $body;
}
?>
为POST任意的body,我们给XMLHttpRequest一个POST的请求类型,并且以send()的参数将body传进来。(注意,对GET请求,send()的参数无效,因为没有body的内容)。
var xhreq = createxmlhttprequest();
xhreq.open("post", "sumPostGeneric.phtml", true);
xhreq.onreadystatechange = function() {
if (xhreq.readystate != 4) { return; }
var serverResponse = xhreq.responsetext;
...
};
xhreq.send("calculate this sum: 5+6");
然而,往往你会想要传送键——值组合(key-value pair),你想要消息看起来就好像它是基于POST的表单被提交。你会这么做,因为这是比较标准的,而且服务器代码库使得接受标准表单数据的Web服务 在编写上比较容易。接下来的服务,sumPostForm.phtml,显示PHP怎样轻而易举地处理这样的提交,对大多数其他语言也一样:
<?
echo $_POST["figure1"] + $_POST["figure2"];
?>
对于要产生CGI风格上传的浏览器脚本而言,需要两个额外的步骤。首先,在一个 "Content-Type"的头部(header)里声明这种风格;例如下面的例子显示的,XMLHttp- Request允许你直接设定请求头部。第二,是让body成为一组一组的名称——值组合(name- value pair):
var xhreq = createxmlhttprequest();
xhreq.open("post", "sumPostForm.phtml", true);
xhReq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhreq.onreadystatechange = function() {
if (xhreq.readystate != 4) { return; }
var serverresponse = xhreq.responsetext;
...
};
xhreq.send("figure1=5&figure2=7");
GET与POST差不多无所不在,但RESTful Service指出,某些时候,某些场合也适合其他的请 求方法,例如PUT和DELETE。对那些其他方法,你不必做任何特殊的工作;只要设定open()调用的请求类型,以及为send()设定合适的 body(在PUT情况下为所放入的项目;在DELETE情况下则为null参数)。
外部域的限制
在探讨XMLHttpRequest时,常见的反应是开始梦想这样一个接口,从各个受欢迎的网站收集内容,并将它们混合成一个 Web 2.0 的大总汇。令人遗憾地,实现这个梦想并非
只是如此简单,因为所有主流浏览器都强加一个关键的安全规则:XMLHttpRequest只能从源服务器访问内容。如果你的应用存在于http://ajax.shop/admin,那么你的 XMLHttp- Request对象可以愉快地访问http://ajax.shop/admin/products.html,但是http://ajax.shop/ products/contents.html就无法到达http://books.ajax.shop/contents.html,并且肯定无法访问http://google.com。
这个“相同源的策略”(same-origin policy)或“相同域的策略”(same-domain policy)(http://www.mozilla.org/projects/security/components/same-origin.html), 对Java applet 与 Flash的开发者是很熟悉的,在那里,此策略一直是适当的,它防止各种类型的滥用,例如,恶意的脚本从服务器抓取保密数据,并且在它们自己的控制下,把 数据上传给另一台服务器。某些人建议这或许矫枉过正,该政策所试图防范的风险都已经有其他方式可以突破(http://spaces.msn.com/members/siteexperts/Blog/cns!1pNcL8JwTfkkjv4gg6LkVCpw! 2085.entry)。然而,像这样的限制不会轻率地被解除;这项规则可能还会存在一段很长的时间,因此我们最好学着与它合作。
以相同来源的限制为前提,那么,所有Ajax混合网站(Ajax mashup site)要怎样运作(http://housingmaps.com)?答案是跨域传送通常通过源服务器运作,源服务器担任一种代理(或信道(tunnel))的角色,让XMLHttpRequest与外部域沟通。“跨越代理”(第10章)详细说明了这种模式,它的“替代方法”一节列出了一些聪明的变通办法,允许绕过源服务器。
XML响应
我们的讨论忽略了房间里的一头大象:XML。XMLHttpRequest,正如它的名字所示,最初是被设计来操作XML 的。如你所见的,它实际上接受任何类型的响应,那么,XML 有什么特别?使用XMLHttpRequest,任何响应都可以通过responseText字段读取,但是还有其他的选择:responseXML。如 果响应头部指明内容是XML,并且响应文字是有效的 XML字符串,则responseXML将是解析XML所得的DOM对象。
显示操作模式(Display Manipulation Pattern)(第5章)已经说明了JavaScript怎样支持DOM 对象的操作。在那些模式内,我们只对一个特别的DOM对象有兴趣,即代表当前网页的HTML(或 XHTML)文档。但你一样也能很容易地操作任何其他的DOM对象。因此,让Web服务输出XML内容,并且操作相对应的DOM对象,有时是很方便的。
这里的前提是输出有效XML的Web服务(见本章稍早部分的内容)。有很多代码库与框架存在,让你从数据库、代码对象、文件,或其他地方自动产生XML。但别认为你必须为建立XML Web服务,开始学习某些花哨的XML代码库,因为自己动手写也相当容
易,至少对简单数据是如此。此服务只需输出一个值为XML的Content-type头部,随后是完整的XML文档。以下是上文所示的相加服务的XML版本——它输出包含输入数字和总和结果的 XML文档:
<?
header("Content-Type: text/xml");
$sum = $_GET["figure1"] + $_GET["figure2"];
echo <<< END_OF_FILE
<sum>
<inputs>
<figure id="1">{$_GET["figure1"]}</figure>
<figure id="2">{$_GET["figure2"]}</figure>
</inputs>
<outputs>$sum</outputs>
</sum>
END_OF_FILE
?>
调用序列和以前一样,但回调函数现在使用responseXML提取结果,接着,包含了一个出色的DOM对象,并且能使用标准DOM API审视它:
var xhReq = createXMLHttpRequest();
xhReq.open("GET", "sumXML.phtml?figure1=10&figure2=20", true);
xhReq.onreadystatechange = function() {
if (xhReq.readyState != 4) { return; }
xml = xhReq.responseXML;
var figure1 = xml.getElementsByTagName("figure")[0].firstChild.nodeValue;
var figure2 = xml.getElementsByTagName("figure")[1].firstChild.nodeValue;
var sum = xml.getElementsByTagName("outputs")[0].firstChild.nodeValue;
...
};
xhReq.send(null);
});
“XMLHttpRequest”这个名字与它的两个主要功能有关:处理HTTP请求以及转换 XML 响应。前者是最关键的,后者则被认为是锦上添花。确实存在一些处理XML响应的好的应用——见“XML消息”(第9章),以及“XML数据岛”与“浏览器 端XSLT”(第11章)——但是切记,XML并非Ajax系统所必需的。
你也可以把XML从浏览器上传到服务器。在这种情况下,XMLHttpRequest不提供任何特别的XML功能;你只是发 送XML消息,就如同发送其他任何消息,以及使用适当的请求类型(例如,POST或PUT)。为了支持接收Web服务,JavaScript一般应该在请 求头部上,声明XML内容的类型:
xhReq.setRequestHeader('Content-Type', "text/xml");
XMLHttpRequest API:摘要
我们已经考察了如何使用XMLHttpRequest来完成典型的任务,现在,这里是根据一篇
文章,Apple Developer Connection(http://developer.apple.com/internet/webcontent/xmlhttpreq. html),所快速摘录出关于XMLHttpRequest的属性和方法。IE5+,Mozilla家族(包括全部的Firefox版本)及Safari 1.2+支持这里所列的API。
XMLHttpRequest具有下列属性:
onreadystatechange
被通知状态变化的回调函数。0表示 UNINITIALIZED(尚未初始化),1表示LOADING(载入中),2表示LOADED(已载入),3表示INTERACTIVE(互动中), 4表示 COMPLETE(已完成)(如先前在“异步调用”里所解释的,状态1至3是含糊的,不同的浏览器有不同的解释)。
readyState
请求的生命周期状态。
responseText
来自服务器的响应,字符串形式。
responseXML
来自服务器的响应,DOM形式,如果响应“Content-Type”的头部是“text/html”,responseText则是有效的XML字符串。
status
从服务器获取的HTTP响应编码(http://www.w3.org/Protocols/rfc2616/rfc2616-sec10. html),正常应该是200;大多数其他值表示错误。
statusText
从服务器收到的HTTP响应编码描述;例如,“Not Found”。
以下XMLHttpRequest的方法
abort()
停止请求,并将它的readyState重设回0。(见本章的“检测错误”。)
getAllResponseHeaders()
返回一个包含全部响应头部的字符串,原本的消息是以换行符号分隔。
getResponseHeader(headerField)
返回特定头部的值。
open(requestMethod, url, asynchronousFlag, username, password)
准备XMLHttpRequest(见上文的“方案”)。后两个参数是选择性的,username和password可用于认证。
send(bodyContent)
与指定的body内容一起发送消息(如果没有body内容要传送则为null;例如,GET 请求)。(见上文的“方案”)
setRequestHeader(headerField, headerValue)
设定请求头部。(见上文的“处理POST与其他请求类型”)
决策
Web服务将提供哪种内容?
正如这个解决方案中所提到的,XML不是XMLHttpRequest能处理的唯一一种内容。只要你能以JavaScript解析该消息,可能存在各式各样的响应类型。Web服务的相关模式强调多种响应类型,包括HTML、XML、JSON和纯文字。
怎样控制缓存?
浏览器可能会将XMLHttpRequest响应缓存起来。有时这种实现正是你想要的,有时并非如此,因此,你必须对缓存加以控制。
讨论缓存控制时,我们谈论的是以GET为基础的请求。使用GET,作为只读的查询;使用其他类型的请求,作为影响服务器状态 的操作。如果你使用POST取得信息,这种信息通常不被缓存。如果你使用GET改变状态,你冒着调用未必总是到达服务器的风险,因为浏览器将在本地缓存这 个调用。还有其他原因也必须遵循这些建议;见“基于RESTful的服务”(第9章)。
为得到最新的服务器信息,你经常想要抑制缓存,在这种情况下,存在一些相关的技术。因为浏览器和服务器的不同,通过组合下列技术,这里的标准建议尽最大可能的适用于所有的网络状况:
l 你可以通过附加时间戳(http://www.howtoadvice.com/StopCaching)让URL具有独特性(有时会使用一个随机的字符串,或自增序列的字符串)。这是一种简单的手法,但是带来了令人吃惊的程序的健壮性和可移植性:
var url = "sum.phtml?figure1=5&figure2=1×tamp=" +
new Date().getTime();
l 你可以为请求添加头部信息:
xhReq.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2005
00:00:00 GMT");
l 在We服务里,设定响应头部信息以抑制缓存(http://www.stridebird.com/ articles/?showarticle=1&id=33)。例如,在PHP里:
header("Expires: Sat, 1 Jan 2005 00:00:00 GMT");
header("Last-Modified: ".gmdate( "D, d M Y H:i:s")."GMT");
header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");
l 使用POST 而不是GET。POST类型的请求有时将导致缓存被抑制。不过,这种特别的技术不被建议,因为,正如RESTful Service所解释的,GET及POST各有其特定的含义,而且两者不应该被视为是可相互交换的。无论如何,它不总是有效,因为某些资源实际上将可能缓存POST响应。
另一方面,当服务耗时且不太可能于最近有所改变时,缓存是个好东西。为了鼓励缓存,
你能推翻上述建议;例如,将Expires头部设成某个合适的未来时间。另外,对较小数据的一种好方法,是使用一种JavaScript数据结构,在程序本身里缓存它。“浏览器缓存”(第13章)将解释如何实现。
怎样处理错误?
在“错误检测”一节中,把一旦发现服务器超时或者非标准错误码时,应该进行什么的处理保留开放。存在3种可能的操作:
再试一下
放弃之前,再试几次。
通知用户
告诉用户什么错了,以及结果是什么。例如,通知他数据没有被提交,在几分钟之后应该再试一次。
什么也不做
有时,你有忽视响应的权利(或缺乏原因)。这可能是因为你正在发起重要性不高的“触发并忽略处理”(Fire-and-Forget)的调用(http://www.ajaxian.com/ archives/2005/09/ajaxian_fire_an.html),在那里,你正在上传一些数据,而无需等待任何响应。
真实世界的实例
Lace聊天室
Brett Stimmerman的Lace Chat(http://www.socket7.net/lace/)是使用XMLHttpRequest的 Ajax聊天应用,采用两种方式实现:上传你键入的消息,并且从服务器下载全部最新消息(图 6-3)。
图6-3:Lace聊天室
Backbase
Backbase的Demo RSS Reader(http://www.backbase.com/demos/RSS)使用XMLHttpRequest 取得最新文章的标题(图6-4)。当点击标题时,新的XMLHttpRequest将取得完整内容。
图6-4:Backbasse的PSS阅读器
Anyterm
Phil Endecott的Anyterm(http://anyterm.org/demos.html)是让你在浏览器内运行telnet或 SSH的Ajax终端机仿真器(terminal emulator)。它利用XMLHttpRequest上传键盘敲击,并下载最新屏幕状态。
Mint
Mint(http://haveamint.com/)是网站统计资料包(package)。网站拥有者在每个页面上包含(include)Mint JavaScript,它悄悄地检查用户的浏览器设定,并利用XMLHttpRequest 上传。
代码范例:AjaxPatterns TestAjaxCaller
在上文“方案”中所引用的范例(http://ajaxpatterns.org/xmlHttpRequestCall/),涵盖大部分典型的XMLHttpRequest用法。实际上,许多人采用Ajax框架与代码库,而不是直接调用XMLHttpRequest。Ajax模式demo使用一个叫作ajaxCaller.js的代码库,该代码库与demo本身平行发展。它是一个相当基本的代码库,但是为需要XMLHttpRequest的
程序,提供一个简单的功能性界面。在这一节里,我将通过显示AjaxCaller Test Demo(http://ajaxify.com/run/ testAjaxCaller)内的一些用法,介绍这个代码库。
最简单的调用获得一些纯文本:只要指定URL与回调函数。
ajaxCaller.getPlainText(url,onResponse);
对于所有的调用而言,回调函数总是接受3个参数。第一个参数是结果,可以是字符串或DOM对象。第2个是对应头部字段 (header field)到头部值(header value)的关联数组。第3个则是“调用的上下文”(calling context)。我们可以将调用的上下文理解成一个伴随着请求和相对应响应往返的选择性的值,当发起调用时,这个值使用你发送给它时完全相同的形式返回 给回调函数。它通常包含调用的相关信息;例如,如果产生调用,送出订购单,调用上下文可能包含被订购的商品。然后,ajaxCaller将该上下文传送给 回调函数,它将此商品标志为订购成功状态。事实上,调用上下文并没有往返服务器;ajaxCaller在本地端保存它,并且追踪每个尚未处理的请求。如果 这听起来有点复杂,请查看“调用跟踪”(第10章)。
回调函数看起来就像这样:
function onResponse(text, headers, callingContext) {
// 使用文字(字符串),头部信息,与调用上下文
}
而且,因为大多数情况下,只有文字被用到,这个函数也可以被声明为更简单的形式。
function onResponse(text) {
// 使用文字(字符串)
}
getPlainText()是4个常用的方法之一,其他还有getXML()、postForPlainText()和 postForXML()。这4个方法同时涵盖了两个常见的请求类型(GET与POST),以及两个响应类型(文字和XML)。
ajaxCaller.getXML(url, callbackFunction);
ajaxCaller.postForXML(url, vars, callbackFunction);
ajaxCaller.getPlainText(url, callbackFunction, callbackContext);
ajaxCaller.postForPlainText(url, callbackFunction, callbackContext);
还有许多通用方法——例如,get()提供更灵活的请求,除了URL和回调函数外,get()允许你指定附加到URL上的一些变量,一个指明响应是否为XML的标志,以及上文所讨论的callingContext。
var vars = {
flavour: "chocolate",
topping: "nuts"
};
ajaxCaller.get("httpLogger.php", vars, onResponse, false, "iceCreamRequest");
对于其他请求类型,也有一般性操作。postVars()创建CGI风格的POST上传,postBody() 创建任意body的POST上传。对于其他请求类型(例如,PUT、TRACE、OPTIONS、DELETE,以及HEAD),也有类似的方法。
替代方法
这一节列出我所知道的全部替代方法,有些作法比其他作法具有更多的限制。为求完整,比较模糊不清的技术性也包含在其中,希望它们可以激发出一些想法。
页面刷新
与服务器通信的传统方法是浏览器请求一个全新的页面,平心而论,这是相当极端的做法。如果用户浏览到网站其他完全不同的部 分,这种方式可能合适,但是如果你只是想在页面底部更新足球比赛的得分,或上传一些用户输入,那就太过头了。最常见的页面刷新类型是超链接 (hyperlink),这将引起浏览器发出GET请求,清理当前页面,并输出响应。另一种页面刷新是表单提交,这将引起浏览器伴随着一些参数发起请求 (GET、POST或其他方式),和使用超链接一样,以新的响应取代之前的页面。使用Web通信,任何用户界面的变化,完全由运行在页面内的脚本决定。这 些传统技术仍然有效,但是大多数服务器通信使用 XMLHttpRequest调用以及相关的技术。
IFrame调用
IFrame调用是XMLHttpRequest主要的替代方法(见本章稍后部分的内容)。就像 XMLHttpRequest一样,它允许远程调用使用GET、POST,及其他请求类型。但是XMLHttpRequest是特别为Web远程通信而设 计的,而IFrame调用利用IFrame做一些其原先并没有真正意图要实现的功能。以下是XMLHttpRequest能力上超越IFrame调用的几 个方面:
l XMLHttpRequest API是特别为Web远程通信设计的,比较容易使用,特别是涉及非GET请求类型。不过,这方面不算大优势,因为一般是建议你使用封装的代码库,避免直接使用API。
l XMLHttpRequest提供了IFrame调用没有的功能,例如,中止调用,以及跟踪调用状态的能力。这可能涉及重要的性能问题(http://www.ajaxian.com/archives/2005/ 09/ajaxian_fire_an.html)。
l XMLHttpRequest一般比较快,特别是响应时间更短(http://me.eae.net/ archive/2005/ 04/02/xml-http-performance-and-caching/)。
l XMLHttpRequest可以以简单、可移植(portable)、优雅的方式来解析XML;而IFrame 与XML毫不相关。
l 在那些确实支持XMLHttpRequest的浏览器上,XMLHttpRequest的API比IFrame的API更一致。
l XMLHttpRequest正迅速获得广泛的熟悉度。这不仅有助于其他用户理解你的代码,同时也表示你将受益于那些监控XMLHttpRequest通信流量的工具(见“流量嗅探”(第18章))。
出于以上这些理由,XMLHttpRequest应该是默认的选择。不过,也存在一些IFrame调用更加适用的特殊场合:
l IFrame在许多实际上不支持XMLHttpRequest的较老浏览器上有效。
l IFrame对浏览历史记录(browser history)和可书签化(bookmarkability)刚好有些专用的属性(至少对IE),如Unique URLs(第17章)所讨论的。
l 如果安全措施将ActiveX关闭,IE 上的XMLHttpRequest将无法正常工作,有时在企业里,这是一个强制实施的策略(http://verens.com/archives/2005/08/12/ajax-in-ie- without-activex/)。
l IFrame可以为HTTPStreaming提供一种更具可移植性的解决方案,正如HTTP Streaming模式所讨论的。
HTTP Streaming
HTTP Streaming(见本章稍后部分的内容)也考虑到Web远程调用,而且和 XMLHttpRequest不同,这种连接持续有效。就功能而言,HTTP Streaming比起XMLHttpRequest的关键优势在于服务器能连续将新信息推送到浏览器。就资源的观点而言,streaming(串流)是 很好的,因为有较少的连接开启与停止,但却存在严重的可量测性(scalability)问题,因为保持大量开启的连接,以及维护大量的服务器端脚本,不 具可行性。
Richer Plugin
Richer Plugin(第8章)模式讨论Java、Flash及其他插件和扩展。这些组件通常拥有权限按计划的调用服务器,并且,在某些情况下,可被使用作为JavaScript代码的有效代理。
On-Demand JavaScript
On-Demand JavaScript(见本章稍后部分的内容)描述两个便捷的方法下载 JavaScript。一个涉及 XMLHttpRequest(因此它本身不是最基础的),另一个是可替代性的传输机制,一种不同的Web远程通信技术。它通过为文档的主要部分 (body)添加脚本元素运行,这种方式可以达到自动获取已命名的JavaScript文件的效果。
Image-Cookie Call
Brent Ashley的RSLite代码库(http://www.ashleyit.com/rs/rslite/)是一个基于图像和 cookie的与众不同的替代方法。图像源(source)属性被设置为服务的URL,这仅仅是一种调用服务的方法。该服务将响应写进一或多个cookie,于是,一旦调用完成,服务的数据将可以从JavaScript访问到。
Stylesheet Call
另一种取得服务器状态的方式为动态改变CSS样式表。就像设定新 JavaScript 或图像源一样,你设定样式表的href特性,指向Web服务。在Julien Lamarre关于这种技术的demo(http://zingzoom.com/ajax/ ajax_with_stylesheet.php)里,Web服务实际上产生样式表,而响应被嵌入background-image属性的URL内!
204响应
一种古老(差不多要过时)的技巧,是让服务器以204“No Content”响应编码响应请求。当浏览器看见这个响应编码时,将不刷新页面,这意味着你的脚本能够安静地提交表单给该服务,对页面没有任何影响。但 是,你只能将204的技巧使用在“触发并忽略处理”(fire-and-forget)的调用——尽管响应可以包含嵌入头部的信息,但是浏览器脚本无法进 行访问。
导入XML文件
存在一种特别的技术可获得XML文档,类似XMLHttpRequest使用的技术。Peter-Paul Koch在2000年描述过这种技术(http://www.quirksmode.org/dom/ importxml.html),并于最近建议它现在可以被取消 (http://www.quirksmode.org/blog/archives/2005/01/with_ httpmapsea.html)。
隐喻
XMLHttpRequest调用看起来就像,当浏览器和用户正在进行主要的交互时,它与服务器之间也有一个附属的交互。