首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

2.2.2 XMLHttp请求

当微软的IE 5.0引入了一个基本的XML支持时,同时引入了一个名为MSXML的ActiveX库(将在第4章更详细地讨论)。该库所提供的XMLHttp对象很快变得十分流行。

通过XMLHttp对象,开发人员可以在应用程序的任何地方初始化HTTP请求。这些请求将以XML格式返回,因而XMLHttp对象提供了一种以XML文档的格式访问信息的简单途径。由于XMLHttp是一个ActiveX控件,因此不仅能够在网页上使用,还可以在任何Windows桌面应用程序中使用;但是在网页上的流行速度要比在桌面应用程序上的流行速度快得多。

随着XMLHttp对象的流行,Mozilla在其浏览器(诸如Firefox)中也实现了XMLHttp的功能。此后不久,Safari(1.2版)和Opera(7.6版)浏览器也复制了Mozilla的实现。现在,四种浏览器都在不同程度上实现了对XMLHttp支持。(Safari和Opera的实现还不完善,对于GET和POST之外的请求还不支持。)

1. 创建XMLHttp对象

使用XMLHttp对象的第一步显然是创建一个对象实例。由于微软将其实现为一个ActiveX控件,因此必须在JavaScript中使用其专有的ActiveXObject类,并传入XMLHttp控件的签名:

var oXmlHttp = new ActiveXObject("Microsoft.XMLHttp");

这行代码创建了第一个版本(即IE 5.0中发布的)的XMLHttp对象。问题是随着MSXML库后续版本的发布,也发布了XMLHttp的几个新版本。每个新版本都更加稳定、速度更快,因此确保在用户机器中使用最新的可用版本。这些签名包括:

◎ Microsoft.XMLHttp

◎ MSXML2.XMLHttp

◎MSXML2.XMLHttp.3.0

◎ MSXML2.XMLHttp.4.0

◎ MSXML2.XMLHttp.5.0

不幸的是,确定使用的最好版本的唯一方法是必须试着逐个创建它们。由于这是一个ActiveX控件,创建对象时发生的所有问题都会抛出一个异常,也就意味着你必须将其包含在try...catch程序块中。该函数最终的结果类似于:

function createXMLHttp() {

var aVersions = [ "MSXML2.XMLHttp.5.0",

"MSXML2.XMLHttp.4.0","MSXML2.XMLHttp.3.0",

"MSXML2.XMLHttp","Microsoft.XMLHttp"

];

for (var i = 0; i < aVersions.length; i++) {

try {

var oXmlHttp = new ActiveXObject(aVersions[i]);

return oXmlHttp;

} catch (oError) {

//不处理

}

}

throw new Error("MSXML is not installed.");

}

createXMLHttp()函数中存储了一个XMLHttp签名的数组,其中最新的签名位于第一个。该函数将遍历这个数组,尝试基于每个签名来创建XMLHttp对象。如果创建失败,catch语句将避免JavaScript错误而使程序停止执行;然后再尝试下一个签名。当创建一个对象之后,将返回该对象。如果函数执行完,还没有成功创建XMLHttp对象,将抛出一个错误以表示创建失败。

幸运的是,在其他浏览器中创建一个XMLHttp对象是很简单的。Mozilla Firefox、Safari和Opera使用的代码都相同:

var oXmlHttp = new XMLHttpRequest();

当然,拥有一种创建XMLHttp对象跨浏览器的方法是很有帮助的。你可以将前面定义的createXMLHttp()函数做如下修改从而创建这样的函数:

function createXMLHttp() {

if (typeof XMLHttpRequest != "undefined") {

return new XMLHttpRequest();

} else if (window.ActiveXObject) {

var aVersions = [ "MSXML2.XMLHttp.5.0",

"MSXML2.XMLHttp.4.0","MSXML2.XMLHttp.3.0",

"MSXML2.XMLHttp","Microsoft.XMLHttp"

];

for (var i = 0; i < aVersions.length; i++) {

try {

var oXmlHttp = new ActiveXObject(aVersions[i]);

return oXmlHttp;

} catch (oError) {

//Do nothing

}

}

}

throw new Error("XMLHttp object could be created.");

}

现在该函数首先检查是否定义了XMLHttpRequest类(通过typeof操作符)。如果已经存在XMLHttpRequest,则用它来创建XMLHttp对象;否则,检查ActiveXObject类是否存在,如果存在则采用与创建IE的XMLHttp相同的方法进行处理。如果这些检查都失败,则抛出一个错误。

创建跨浏览器兼容的XMLHttp对象的另一种方法是使用已编写好跨浏览器代码的库。本书的两位作者开发的zXml库就是这样的库,它可以在www.nczonline.net/downloads/中下载。该库为创建XMLHttp对象定义了一个函数:

var oXmlHttp = zXmlHttp.createRequest();

本书将使用createRequest()函数以及zXml库本身来帮助Ajax技术实现跨浏览器处理。

2. 使用XMLHttp

在创建XMLHttp对象之后,就可以开始在JavaScript中发起HTTP请求了。第一步是调用open()方法来初始化该对象。该方法可以接受以下参数:

◎请求类型(request type):说明所发送的请求类型的字符串——通常是GET或POST(这也是现在所有浏览器都支持的类型);

◎URL: 说明发送请求的目标URL的字符串;

◎ async:布尔值,用来说明请求是否为异步模式。

最后一个参数async是很重要的,因为它是用来控制JavaScript如何执行该请求。当设置为true时,将以异步模式发送该请求,JavaScript代码将继续执行而不再等待响应,且必须使用一个事件处理函数来监控请求的响应。如果将async设置为false,则将以同步模式发送该请求, JavaScript将等接收到响应后再继续执行剩余代码。这意味着如果响应时间很长,则用户在浏览器收到响应之前是将无法与其交互的。基于这个原因,Ajax应用程序开发的最佳实践是,使用异步请求来实现数据获取,使用同步请求来实现与服务器之间发送和接收简单的消息。

如果要以异步模式向info.txt发出请求,则最先要做的事是:

var oXmlHttp = zXmlHttp.createRequest();

oXmlHttp.open("get", "info.txt", true);

注意,这个例子中的第一个参数是请求类型,虽然请求类型通常用大写字母定义,但在这里并不区分大小写。

紧接下来,需要定义一个onreadystatechange事件处理函数。XMLHttp对象有一个名为readyState的属性,该属性从请求发送到接收响应期间会发生变化。readyState共有5种可能的取值:

◎0 (uninitialized,未初始化): 对象已经创建,但还没有调用open()方法;

◎1 (loading,载入中): open()方法已经调用,但请求还没有发送;

◎2 (loaded,已载入): 请求已经发送;

◎3 (interactive,交互中): 已经接收到部分响应;

◎ 4 (complete,完成): 所有数据都已经收到,连接已经关闭。

每当readyState属性的值发生变化时,就将触发readystatechange事件,并调用onreadystatechange事件处理函数。由于浏览器的实现不同,要实现跨浏览器开发,则可靠的readyState的值是0、1和4。在大部分情况下,当返回请求时只需检查值是否为4。

var oXmlHttp = zXmlHttp.createRequest();

oXmlHttp.open("get", "info.txt", true);

oXmlHttp.onreadystatechange = function () {

if (oXmlHttp.readyState == 4) {

alert("Got response.");

}

};

最后一步是调用send()方法,它将完成请求的发送。该方法只接受一个参数,即表示请求主体的字符串。如果请求不包含主体(应该记得,GET请求就不包含),则必须传入null:

var oXmlHttp = zXmlHttp.createRequest();

oXmlHttp.open("get", "info.txt", true);

oXmlHttp.onreadystatechange = function () {

if (oXmlHttp.readyState == 4) {

alert("Got response.");

}

};

oXmlHttp.send(null);

好了!请求发送并接收到响应时将显示出一个警告框(alert)。但仅将请求已经接收到的信息显示出来并没有太大用处。XMLHttp强大之处主要体现在可以访问返回的数据、响应的状态以及响应的首部。

要获得从请求返回的数据,可以使用responseText或responseXML属性。responseText属性返回一个包含响应主体的字符串,而responseXML属性则返回一个XML文档对象,它仅当返回的内容类型为text/xml时才使用。(XML文档对象将在第4章详细说明。)因此,要获取info.txt中的文本,调用应为:

var sData = oXmlHttp.responseText;

注意,仅当文件找到了并且没有发生错误,才能够返回info.txt中的文本。假设info.txt不存在,那么responseText将包含服务器的404消息。幸运的是,我们还有办法判断是否出现了错误。

status属性中包含了响应中的HTTP状态码,而statusText则包含对该状态的文字描述(诸如“OK”或“Not Found”)。使用这两个属性,就可以确保接收的数据是你实际需要的数据,或者告诉用户为什么查找不到所要的数据:

if (oXmlHttp.status == 200) {

alert("Data returned is: " + oXmlHttp.responseText;

} else {

alert("An error occurred: " + oXmlHttp.statusText;

}

通常,总是确保响应的状态是200,这说明请求已经成功地完成了。即使服务器端发生了错误,readyState属性的值仍然会设置为4,因此只检查它是不够的。在本例中,只有当states的值是200时才显示responseText属性的内容;否则将显示错误消息。

在Opera中并没有实现statusText属性,并且有时在其他浏览器中会返回一个错误的描述。因此永远不要只依赖于statusText来判断是否出现错误。

正如前面所提到的,你还可以访问响应的首部。使用getResponeHeader()方法,并传入需要获得的首部名称,就可以获得一个特定的首部值。最有用的响应首部是Content-Type,它将告诉你所发送数据的格式:

var sContentType = oXmlHttp.getResponseHeader("Content-Type");

if (sContentType == "text/xml") {

alert("XML content received.");

} else if (sContentType == "text/plain") {

alert("Plain text content received.");

} else {

alert("Unexpected content received.");

}

这个代码片段用来检查响应的内容类型,并通过一个警告框来显示返回的数据类型。通常,从服务器可能只接收XML数据(内容类型为text/xml)或纯文本(内容类型为text/plain),因为只有这两种内容类型可以使用JavaScript更容易地处理。

如果你想看到从服务器中返回的所有首部,则可以使用getAllResponseHeaders()方法,它将简单地返回一个包含所有首部的字符串。在这个字符串中每个首部可以用一个换行字符(在JavaScript中表示为\n),或一个回车加换行字符(在JavaScript中表示为\r\n)隔开,因此可以像下面这样来处理每个首部:

var sHeaders = oXmlHttp.getAllResponseHeaders();

var aHeaders = sHeaders.split(/\r?\n/);

for (var i=0; i < aHeaders.length; i++) {

alert(aHeaders[i]);

}

在这个例子中,我们使用JavaScript中字符串类型的split()方法,并传入一个正则表达式(通过回车/换行或换行来匹配),将每个首部字符串分解出来,存到一个首部数组中。现在你就可以遍历所有的首部并按自己的想法来处理。注意,在aHeaders数组中每个字符串的格式都是headername:headervalue(首部名:首部值)。

在发送之前,我们还能够设置请求的首部。你可以在发送时说明数据的内容类型,或者需要发送一些服务器在处理该请求时需要的一些额外数据。要实现这一目标,可以在调用send()之前使用SetRequestHeader()方法:

var oXmlHttp = zXmlHttp.createRequest();

oXmlHttp.open("get", "info.txt", true);

oXmlHttp.onreadystatechange = function () {

if (oXmlHttp.readyState == 4) {

alert("Got response.");

}

};

oXmlHttp.setRequestHeader("myheader", "myvalue");

oXmlHttp.send(null);

在这段代码中,在发送之前将在请求中添加了一个名为myheader的首部。该首部将以myheader:myvalue的默认格式进行添加。

到现在为止,你已经知道如何实现在大多数情况都能很好应用的异步请求了。发送同步请求则意味着无需指定onreadystatechange事件处理函数,因为在send()方法返回时将接收到其响应。因此,你可能像这样进行处理:

var oXmlHttp = zXmlHttp.createRequest();

oXmlHttp.open("get", "info.txt", false);

oXmlHttp.send(null);

if (oXmlHttp.status == 200) {

alert("Data returned is: " + oXmlHttp.responseText;

} else {

alert("An error occurred: " + oXmlHttp.statusText;

}

用同步模式来发送该请求(将open()方法的第三个参数设置为false),可以使你在调用send()方法之后马上对其响应进行处理。这对于想让用户交互等待响应,或希望只接收很少的数据(例如,小于1KB)的应用场景是很有用的。而对于通常的数据量或较大的数据量而言,最好还是使用异步调用。

3. XMLHttp的 GET请求

现在我们再来考虑用隐藏帧发送GET请求的例子,看看如何使用XMLHttp来改进它。第一个要修改的是GetCustomerData.php,必须将其从返回一个HTML页面改为返回一个HTML片段。现在整个文件就显得更加简练了:

<?php

header("Content-Type: text/plain");

$sID = $_GET["id"];

$sInfo = "";

$sDBServer = "your.databaser.server";

$sDBName = "your_db_name";

$sDBUsername = "your_db_username";

$sDBPassword = "your_db_password";

$sQuery = "Select * from Customers where CustomerId=".$sID;

$oLink = mysql_connect($sDBServer,$sDBUsername,$sDBPassword);

@mysql_select_db($sDBName) or $sInfo="Unable to open database";

if($oResult = mysql_query($sQuery) and mysql_num_rows($oResult) > 0) {

$aValues = mysql_fetch_array($oResult,MYSQL_ASSOC);

$sInfo = $aValues['Name']."<br />".$aValues['Address']."<br />".

$aValues['City']."<br />".$aValues['State']."<br />".

$aValues['Zip']."<br /><br />Phone: ".$aValues['Phone']."<br />".

"<a href=\"mailto:".$aValues['E-mail']."\">".

$aValues['E-mail']."</a>";

} else {

$sInfo = "Customer with ID $sID doesn't exist.";

}

mysql_close($oLink);

echo $sInfo;

?>

正如你所见,在该页面中没有可见的HTML或JavaScript调用。所有的主要逻辑还是相同的,但添加了两行新的PHP代码。第一处在开始位置,使用header()函数来设置页面的内容类型。即便该页面将返回一个HTML片段,但最好还是将内容类型设置为text/plain,因为它不是一个完整的HTML页面(因此可能不是有效的HTML)。对于向浏览器发送非HTML格式数据的页面,都应该设置为该内容类型。第二处则是接近最后面,使用echo命令将变量$sInfo的值输出到流。

在HTML主页面中,其基本的设置是:

<p>Enter customer ID number to retrieve information:</p>

<p>Customer ID: <input type="text" id="txtCustomerId" value="" /></p>

<p><input type="button" value="Get Customer Info"

onclick="requestCustomerInfo()" /></p>

<div id="divCustomerInfo"></div>

requestCustomerInfo()函数先前创建的是隐藏iframe,但现在必须改为使用XMLHttp:

function requestCustomerInfo() {

var sId = document.getElementById("txtCustomerId").value;

var oXmlHttp = zXmlHttp.createRequest();

oXmlHttp.open("get", "GetCustomerData.php?id=" + sId, true);

oXmlHttp.onreadystatechange = function () {

if (oXmlHttp.readyState == 4) {

if (oXmlHttp.status == 200) {

displayCustomerInfo(oXmlHttp.responseText);

} else {

displayCustomerInfo("An error occurred: " + oXmlHttp.statusText);

}

}

};

oXmlHttp.send(null);

}

可以看到这个函数以相同的方法开始,都是先获取用户输入的ID。然后,使用zXml库创建一个XMLHttp对象。接着,调用open()方法,指定以异步模式发出对GetCustomerData.php(并且将上面提到的ID附加到查询字符串后)的GET请求。紧接着将任务交给事件处理函数,由它来检查readyState的值是否为4,以及该请求的status。如果请求成功(status的值是200),那么将调用displayCustomerInfo()函数,并将响应主体(通过responseText访问)作为参数传入。如果发生了错误(status的值不是200),那么将其错误信息作为参数传给display CustomerInfo()函数。

与隐藏帧/iframe的例子相比有几处不同。首先,不需要主页面之外的JavaScript代码。这很重要,因为任何时候将代码保存在两个地方,总是可能出现不兼容的情况;在基于帧的例子中,依赖于分别位于显示页面和隐藏帧中的脚本之间的通信。通过将GetCustomerInfo.php改为只返回你感兴趣的数据,就可以消除在这些位置之间调用JavaScript的潜在问题。第二个不同是,当请求执行出现问题时很容易获知。在前一个例子中,当请求处理过程出现错误时,没有一个机制来标识和响应服务器端错误。使用XMLHttp,所有的服务器端错误都将展现给开发人员,可以给用户提供一个有意义的错误反馈。针对页面中的HTTP请求,XMLHttp是比隐藏帧技术更优秀的解决方案。

4. XMLHttp的POST请求

现在你已经知道XMLHttp是如何处理GET请求了,接下来看看如何处理POST请求。首先,必须以修改GetCustomerInfo.php的方式对SavaCustomer.php做出同样的修改,也就意味着需要去除无关的HTML和JavaScript,添加内容类型信息,并将其输出到文本中:

<?php

header("Content-Type: text/plain");

$sName = $_POST["txtName"];

$sAddress = $_POST["txtAddress"];

$sCity = $_POST["txtCity"];

$sState = $_POST["txtState"];

$sZipCode = $_POST["txtZipCode"];

$sPhone = $_POST["txtPhone"];

$sEmail = $_POST["txtEmail"];

$sStatus = "";

$sDBServer = "your.database.server";

$sDBName = "your_db_name";

$sDBUsername = "your_db_username";

$sDBPassword = "your_db_password";

$sSQL = "Insert into Customers(Name,Address,City,State,Zip,Phone,`E-mail`) ".

" values ('$sName','$sAddress','$sCity','$sState', '$sZipCode'".

", '$sPhone', '$sEmail')";

$oLink = mysql_connect($sDBServer,$sDBUsername,$sDBPassword);

@mysql_select_db($sDBName) or $sStatus = "Unable to open database";

if($oResult = mysql_query($sSQL)) {

$sStatus = "Added customer; customer ID is ".mysql_insert_id();

} else {

$sStatus = "An error occurred while inserting; customer not saved.";

}

mysql_close($oLink);

echo $sStatus;

?>

上面就是SavaCustomer.php完整的代码。注意,调用函数header()是用来设置内容类型,而echo命令则是用来输出变量$sStatus的值。

在主页面中,只是设置一个让用户输入新客户信息的简单表单,如下所示:

<form method="post" action="SaveCustomer.php"

onsubmit="sendRequest(); return false">

<p>Enter customer information to be saved:</p>

<p>Customer Name: <input type="text" name="txtName" value="" /><br />

Address: <input type="text" name="txtAddress" value="" /><br />

City: <input type="text" name="txtCity" value="" /><br />

State: <input type="text" name="txtState" value="" /><br />

Zip Code: <input type="text" name="txtZipCode" value="" /><br />

Phone: <input type="text" name="txtPhone" value="" /><br />

E-mail: <input type="text" name="txtEmail" value="" /></p>

<p><input type="submit" value="Save Customer Info" /></p>

</form>

<div id="divStatus"></div>

你会发现onsubmit事件处理函数现在改为调用sendRequest()函数(尽管事件处理函数仍然返回false,以阻止实际的表单提交)。该方法首先为POST请求组装数据,然后创建一个XMLHttp对象来发送这个请求。该数据必须按下列的查询字符串格式来发送:

name1=value1&name2=value2&name3=value3

每个参数的名字和值都必须转化为URL编码格式,以避免在传输过程中丢失数据。JavaScript提供了一个名为encodeURLComponent()的内建函数,通过它可以完成这个编码转换。为了创建这个字符串,需要遍历表单的所有字段,提取名字和值并对其进行编码转换。这些将交由getRequestBody()函数来处理:

function getRequestBody(oForm) {

var aParams = new Array();

for (var i=0 ; i < oForm.elements.length; i++) {

var sParam = encodeURIComponent(oForm.elements[i].name);

sParam += "=";

sParam += encodeURIComponent(oForm.elements[i].value);

aParams.push(sParam);

}

return aParams.join("&");

}

该函数假定将一个表单的引用作为参数传入。它将创建一个数组(aParams)来存储每个名字—值数据对。然后遍历表单的每个元素,针对每个元素生成一个字符串并存到sParam变量中,再将该变量添加到数组中。这样做可以避免多次字符串连接操作,这种操作在一些浏览器中会降低代码的执行速度。最后一步则是调用数组的join()方法,并将符号&作为参数传入,这实际上是通过符号&将所有的名字—值对组合起来,生成格式正确的一个字符串。

字符串连接操作对于大多数浏览器而言都是一个代价很高的操作,这是因为字符串是不可变的,即一旦创建就不能够改变其值。因此要连接两个字符串,首先要创建一个新的字符串,然后将两个字符串的内容复制到其中。重复这种操作过程,将会使服务器的处理速度下降。正是因为这个原因,最好只对较小的字符串进行连接操作,而对于较长的字符串则使用array的join()方法。

sendRequest()函数将调用getRequestBody()并配置请求:

function sendRequest() {

var oForm = document.forms[0];

var sBody = getRequestBody(oForm);

var oXmlHttp = zXmlHttp.createRequest();

oXmlHttp.open("post", oForm.action, true);

oXmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

oXmlHttp.onreadystatechange = function () {

if (oXmlHttp.readyState == 4) {

if (oXmlHttp.status == 200) {

saveResult(oXmlHttp.responseText);

} else {

saveResult("An error occurred: " + oXmlHttp.statusText);

}

}

};

oXmlHttp.send(sBody);

}

与前面的例子一样,该函数的第一步也是获取表单的引用并将其存到一个变量(oForm)中。然后,将生成的请求主体存到变量sBody中。接下来则是创建和配置XMLHttp对象。注意,现在open()函数的第一个参数不是get而是post,第二个参数变成了oForm.action(该脚本可以用在多个页面)。你可能还会注意到已经对请求首部进行了设置。当表单从浏览器传送到服务器时,将会把请求的内容类型设置为application/x-www-form-urlencoded。大多数服务器端语言都需要这种编码格式,才能够按照它对收到的POST数据进行正确解析,因此对其进行设置是很重要的。

而onreadystatechange事件处理函数则与GET请求例子中的十分类似;唯一的变化是将调用的函数从displayCustomerInfo()改为savaResult()。最后一行是很重要的,它将sBody变量的内容作为字符串传给send()函数,这样它就将成为请求主体的一部分。这有效地模拟了浏览器的操作,因此所有的服务器端程序逻辑将能够按预期意愿完成工作。

5. XMLHttp的优点和缺点

显然,你能够感受到使用XMLHttp替代隐藏帧技术来实现客户端—服务器通信的优点。编写的代码很清晰,而且代码的意图也比使用隐藏帧中大量的回调函数更易于理解。不仅可以访问请求和响应首部,还能够访问HTTP状态码,这使你可以判断出请求处理是否成功。

不利的一面是,它不像隐藏帧技术,当发出调用时并没有浏览器的历史记录保存下来。浏览器的后退和前进按钮并没有和XMLHttp请求绑定在一起,因此将会使其失去效用。正是因为这个原因,许多Ajax应用程序将XMLHttp和隐藏帧技术结合使用,以生成一个更加可用的用户界面。

另一个缺点只体现在IE上,它要求必须启用ActiveX控件。如果用户将你的页面设置为特定的安全区域[1],该区域禁用ActiveX控件,这将使得你无法访问XMLHttp对象。在这种情况下,可能只能够使用隐藏帧技术。

[1]. 即IE菜单的“工具”à“Internet选项”à“安全”设置中的安全区域,包括Internet、本地Intranet、受信任站点及受限制的站点四种。——译者注

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论