对于很多Web开发人员来说,只需要生成简单的请求并接收简单的响应即可;但是对于希望掌握Ajax的开发人员来说,必须要全面理解HTTP状态代码、就绪状态和XMLHttpRequest对象。在本文中,Brett McLaughlin将向您介绍各种状态代码,并展示浏览器如何对其进行处理,本文还给出了在Ajax中使用的比较少见的HTTP请求。
在本系列的上篇文章中,我们将详细介绍XMLHttpRequest对象,它是Ajax应用程序的中心,负责处理服务器端应用程序和脚本的请求,并处理从服务器端组件返回的数据。由于所有的Ajax应用程序都要使用XMLHttpRequest对象,因此您可能会希望熟悉这个对象,从而能够让Ajax执行得更好。
在本文中,我将在上一篇文章的基础上重点介绍这个请求对象的3个关键部分的内容:
·HTTP就绪状态
·HTTP状态代码
·可以生成的请求类型
这三部分内容都是在构造一个请求时所要考虑的因素;但是介绍这些主题的内容太少了。然而,如果您不仅仅是想了解Ajax编程的常识,而是希望了解更多内容,就需要熟悉就绪状态、状态代码和请求本身的内容。当应用程序出现问题时——这种问题总是存在——那么如果能够正确理解就绪状态、如何生成一个HEAD请求或者400的状态代码的确切含义,就可以在5分钟内调试出问题,而不是在各种挫折和困惑中度过5个小时。
下面让我们首先来看一下HTTP就绪状态。
深入了解HTTP就绪状态
您应该还记得在上一篇文章中XMLHttpRequest对象有一个名为readyState的属性。这个属性确保服务器已经完成了一个请求,通常会使用一个回调函数从服务器中读出数据来更新Web表单或页面的内容。清单1给出了一个简单的例子(这也是本系列的上一篇文章中的一个例子——请参见 参考资料)。
清单1. 在回调函数中处理服务器的响应
function updatePage() {
if (request.readyState == 4) {
if (request.status == 200) {
var response = request.responseText.split(“|”);
document.getElementById(“order”).value = response[0];
document.getElementById(“address”).innerHTML =
response[1].replace(/n/g, “<br />”);
} else
alert(“status is ” + request.status);
}
}
这显然是就绪状态最常见(也是最简单)的用法。正如您从数字”4″中可以看出的一样,还有其他几个就绪状态(您在上一篇文章中也看到过这个清单——请参见参考资料):
·请求未初始化(还没有调用open())。
·请求已经建立,但是还没有发送(还没有调用send())。
·请求已发送,正在处理中(通常现在可以从响应中获取内容头)。
·请求在处理中;通常响应中已有部分数据可用了,但是服务器还没有完成响应的生成。
·响应已完成;您可以获取并使用服务器的响应了。
如果您希望不仅仅是了解Ajax编程的基本知识,那么就不但需要知道这些状态,了解这些状态是何时出现的,以及如何来使用这些状态。首先,您需要学习在每种就绪状态下可能碰到的是哪种请求状态。不幸的是,这一点并不直观,而且会涉及几种特殊的情况。
隐秘就绪状态
第一种就绪状态的特点是readyState属性为0(readyState==0),表示未初始化状态。一旦对请求对象调用open()之后,这个属性就被设置为1。由于您通常都是在一对请求进行初始化之后就立即调用open(),因此很少会看到readyState==0的状态。另外,未初始化的就绪状态在实际的应用程序中是没有真正的用处的。
不过为了满足我们的兴趣,请参见清单2的内容,其中显示了如何在readyState被设置为0时来获取这种就绪状态。
清单2. 获取0就绪状态
function getSalesData() {
// create a request object
createRequest();
alert(“Ready state is: ” + request.readyState);
// Setup (initialize) the request
var url = “/boards/servlet/updateBoardSales”;
request.open(“GET”, url, true);
request.onreadystatechange = updatePage;
request.send(null);
}
在这个简单的例子中,getSalesData()是Web页面调用来启动请求(例如点击一个按钮时)所使用的函数。注意您必须在调用open()之前来查看就绪状态。图1给出了运行这个应用程序的结果。
图1. 就绪状态0
显然,这并不能为您带来多少好处;需要确保尚未调用open()函数的情况很少。在大部分Ajax编程的真实情况中,这种就绪状态的唯一用法就是使用相同的XMLHttpRequest对象在多个函数之间生成多个请求。在这种(不常见的)情况中,您可能会在生成新请求之前希望确保请求对象是处于未初始化状态(readyState==0)。这实际上是要确保另外一个函数没有同时使用这个对象。
查看正在处理的请求的就绪状态
除了0就绪状态之外,请求对象还需要依次经历典型的请求和响应的其他几种就绪状态,最后才以就绪状态4的形式结束。这就是为什么您在大部分回调函数中都可以看到if(request.readyState==4)这行代码;它确保服务器已经完成对请求的处理,现在可以安全地更新Web页面或根据从服务器返回来的数据来进行操作了。
要查看这种状态发生的过程非常简单。如果就绪状态为4,我们不仅要运行回调函数中的代码,而且还要在每次调用回调函数时都输出就绪状态。 清单3给出了一个实现这种功能的例子。
清单3. 查看就绪状态
function updatePage() {
// Output the current ready state
alert(“updatePage() called with ready state of ” + request.readyState);
}
如果您不确定如何运行这个函数,就需要创建一个函数,然后在Web页面中调用这个函数,并让它向服务器端的组件发送一个请求(例如清单2给出的函数,或本系列文章的第1部分和第2部分中给出的例子)。确保在建立请求时,将回调函数设置为updatePage();要实现这种设置,可以将请求对象的onreadystatechange属性设置为updatePage()。
这段代码就是onreadystatechange意义的一个确切展示——每次请求的就绪状态发生变化时,就调用updatePage(),然后我们就可以看到一个警告了。图2给出了一个调用这个函数的例子,其中就绪状态为1。
图2. 就绪状态1
您可以自己尝试运行这段代码。将其放入Web页面中,然后激活事件处理程序(单击按钮,在域之间按tab键切换焦点,或者使用设置的任何方法来触发请求)。这个回调函数会运行多次——每次就绪状态都会改变——您可以看到每个就绪状态的警告。这是跟踪请求所经历的各个阶段的最好方法。
浏览器的不一致性
在对这个过程有一个基本的了解之后,请试着从几个不同的浏览器中访问您的页面。您应该会注意到各个浏览器如何处理这些就绪状态并不一致。例如,在Firefox 1.5中,您会看到以下就绪状态:
·1
·2
·3
·4
这并不奇怪,因为每个请求状态都在这里表示出来了。然而,如果您使用Safari来访问相同的应用程序,就应该看到——或者看不到——一些有趣的事情。下面是在Safari 2.0.1中看到的状态:
·2
·3
·4
Safari实际上把第一个就绪状态给丢弃了,也并没有什么明显的原因说明为什么要这样做;不过这就是Safari的工作方式。这还说明了一个重要的问题:尽管在使用服务器上的数据之前确保请求的状态为4是一个好主意,但是依赖于每个过渡期就绪状态编写的代码的确会在不同的浏览器上得到不同的结果。
例如,在使用Opera 8.5时,所显示的就绪状态情况就更加糟糕了:
·3
·4
最后,Internet Explorer会显示如下状态:
·1
·2
·3
·4
如果您碰到请求方面的问题,这就是用来发现问题的 首要之处。最好的方式是在Internet Explorer和Firefox都进行一下测试——您会看到所有这4种状态,并可以检查请求的每个状态所处的情况。
接下来我们再来看一下响应端的情况。
显微镜下的响应数据
一旦我们理解在请求过程中发生的各个就绪状态之后,接下来就可以来看一下XMLHttpRequest对象的另外一个方面了——responseText属性。回想一下在上一篇文章中我们介绍过的内容,就可以知道这个属性用来从服务器上获取数据。一旦服务器完成对请求的处理之后,就可以将响应请求数据所需要的任何数据放到请求的responseText中了。然后回调函数就可以使用这些数据,如清单1和清单4所示。
清单4. 使用服务器上返回的响应
function updatePage() {
if (request.readyState == 4) {
var newTotal = request.responseText;
var totalSoldEl = document.getElementById(“total-sold”);
var netProfitEl = document.getElementById(“net-profit”);
replaceText(totalSoldEl, newTotal);
/* 图 out the new net profit */
var boardCostEl = document.getElementById(“board-cost”);
var boardCost = getText(boardCostEl);
var manCostEl = document.getElementById(“man-cost”);
var manCost = getText(manCostEl);
var profitPerBoard = boardCost – manCost;
var netProfit = profitPerBoard * newTotal;
/* update the net profit on the sales form */
netProfit = Math.round(netProfit * 100) / 100;
replaceText(netProfitEl, netProfit);
}
清单1相当简单;清单4稍微有点复杂,但是它们在开始时都要检查就绪状态,并获取responseText属性的值。
查看请求的响应文本
与就绪状态类似,responseText属性的值在整个请求的生命周期中也会发生变化。要查看这种变化,请使用如清单5所示的代码来测试请求的响应文本,以及它们的就绪状态。
清单5. 测试responseText属性
function updatePage() {
// Output the current ready state
alert(“updatePage() called with ready state of ” + request.readyState +
” and a response text of ’” + request.responseText + ”’”);
}
现在在浏览器中打开Web应用程序,并激活您的请求。要更好地看到这段代码的效果,请使用Firefox或Internet Explorer,因为这两个浏览器都可以报告出请求过程中所有可能的就绪状态。例如在就绪状态2中,就没有定义responseText(请参见图3);如果JavaScript控制台也已经打开了,您就会看到一个错误。
图3. 就绪状态为2的响应文本
不过在就绪状态3中,服务器已经在responseText属性中放上了一个值,至少在这个例子中是这样(请参见图4)。
图4. 就绪状态为3的响应文本
您会看到就绪状态为3的响应在每个脚本、每个服务器甚至每个浏览器上都是不一样的。不过,这在调试应用程序中依然是非常有用的。
获取安全数据
所有的文档和规范都强调,只有在就绪状态为4时数据才可以安全使用。相信我,当就绪状态为3时,您很少能找到无法从responseText属性获取数据的情况。然而,在应用程序中将自己的逻辑依赖于就绪状态3可不是什么好主意——一旦您编写了依赖于就绪状态3的完整数据的的代码,几乎就要自己来负责当时的数据不完整问题了。
比较好的做法是向用户提供一些反馈,说明在处于就绪状态3时,很快就会有响应了。尽管使用alert()之类的函数显然不是什么好主意——使用Ajax然后使用一个警告对话框来阻塞用户显然是错误的——不过您可以在就绪状态发生变化时更新表单或页面中的域。例如,对于就绪状态1来说要将进度指示器的宽度设置为25%,对于就绪状态2来说要将进度指示器的宽度设置为50%,对于就绪状态3来说要将进度指示器的宽度设置为75%,当就绪状态为4时将进度指示器的宽度设置为100%(完成)。
当然,正如您已经看到的一样,这种方法非常聪明,但它是依赖于浏览器的。在Opera上,您永远都不会看到前两个就绪状态,而在Safari上则没有第一个(1)。由于这个原因,我将这段代码留作练习,而没有在本文中包括进来。
现在应该来看一下状态代码了。
深入了解HTTP状态代码
有了就绪状态和您在Ajax编程技术中学习到的服务器的响应,您就可以为Ajax应用程序添加另外一级复杂性了——这要使用HTTP状态代码。这些代码对于Ajax来说并没有什么新鲜。从Web出现以来,它们就已经存在了。在Web浏览器中您可能已经看到过几个状态代码:
·401:未经授权
·403:禁止
·404:没找到
您可以找到更多的状态代码(完整清单请参见参考资料)。要为Ajax应用程序另外添加一层控制和响应(以及更为健壮的错误处理)机制,您需要适当地查看请求和响应中的状态代码。
200:一切正常
在很多Ajax应用程序中,您将看到一个回调函数,它负责检查就绪状态,然后继续利用从服务器响应中返回的数据,如清单6所示。
清单6. 忽略状态代码的回调函数
function updatePage() {
if (request.readyState == 4) {
var response = request.responseText.split(“|”);
document.getElementById(“order”).value = response[0];
document.getElementById(“address”).innerHTML =
response[1].replace(/n/g, “<br />”);
}
}
这对于Ajax编程来说证明是一种短视而错误的方法。如果脚本需要认证,而请求却没有提供有效的证书,那么服务器就会返回诸如403或401之类的错误代码。然而,由于服务器对请求进行了应答,因此就绪状态就被设置为4(即使应答并不是请求所期望的也是如此)。最终,用户没有获得有效数据,当JavaScript试图使用不存在的服务器数据时就可能会出现严重的错误。
它花费了最小的努力来确保服务器不但完成了一个请求,而且还返回了一个“一切良好”的状态代码。这个代码是”200″,它是通过XMLHttpRequest对象的status属性来报告的。为了确保服务器不但完成了一个请求,而且还报告了一个OK状态,请在您的回调函数中添加另外一个检查功能,如清单7所示。
清单7. 检查有效状态代码
function updatePage() {
if (request.readyState == 4) {
if (request.status == 200) {
var response = request.responseText.split(“|”);
document.getElementById(“order”).value = response[0];
document.getElementById(“address”).innerHTML =
response[1].replace(/n/g, “<br />”);
} else
alert(“status is”+request.status);
}
}
通过添加这几行代码,您就可以确认是否存在问题,用户会看到一个有用的错误消息,而不仅仅是看到一个由断章取义的数据所构成的页面,而没有任何解释。
重定向和重新路由
在深入介绍有关错误的内容之前,我们有必要来讨论一下有关一个在使用Ajax时并不需要 关心的问题——重定向。在HTTP状态代码中,这是300系列的状态代码,包括:
·301:永久移动
·302:找到(请求被重新定向到另外一个URL/URI上)
·305:使用代理(请求必须使用一个代理来访问所请求的资源)
Ajax程序员可能并不太关心有关重定向的问题,这是由于两方面的原因:
·首先,Ajax应用程序通常都是为一个特定的服务器端脚本、servlet或应用程序而编写的。对于那些您看不到就消失了的组件来说,Ajax程序员就不太清楚了。因此有时您会知道资源已经移动了(因为您移动了它,或者通过某种手段移动了它),接下来要修改请求中的URL,并且不会再碰到这种结果了。
·更为重要的一个原因是:Ajax应用程序和请求都是封装在沙盒中的。这就意味着提供生成Ajax请求的Web页面的域必须是对这些请求进行响应的域。因此ebay.com所提供的Web页面就不能对一个在amazon.com上运行的脚本生成一个Ajax风格的请求;在ibm.com上的Ajax应用程序也无法对在netbeans.org上运行的servlets发出请求。
结果是您的请求无法重定向到其他服务器上,而不会产生安全性错误。在这些情况中,您根本就不会得到状态代码。通常在调试控制台中都会产生一个JavaScript错误。因此,在对状态代码进行充分的考虑之后,您就可以完全忽略重定向代码的问题了。
错误
一旦接收到状态代码200并且意识到可以很大程度上忽略300系列的状态代码之后,所需要担心的唯一一组代码就是400系列的代码了,这说明了不同类型的错误。回头再来看一下清单7,并注意在对错误进行处理时,只将少数常见的错误消息输出给用户了。尽管这是朝正确方向前进的一步,但是要告诉从事应用程序开发的用户和程序员究竟发生了什么问题,这些消息仍然是没有太大用处的。
首先,我们要添加对找不到的页的支持。实际上这在大部分产品系统中都不应该出现,但是在测试脚本位置发生变化或程序员输入了错误的URL时,这种情况并不罕见。如果您可以自然地报告404错误,就可以为那些困扰不堪的用户和程序员提供更多帮助。例如,如果服务器上的一个脚本被删除了,我们就可以使用清单7中的代码,这样用户就会看到一个如图5所示的非描述性错误。
图5. 常见错误处理
用户无法判断问题究竟是认证问题、没找到脚本(此处就是这种情况)、用户错误还是代码中有些地方产生了问题。添加一些简单的代码可以让这个错误更加具体。请参照清单8,它负责处理没找到的脚本或认证发生错误的情况,在出现这些错误时都会给出具体的消息。
清单8. 检查有效状态代码
function updatePage() {
if (request.readyState == 4) {
if (request.status == 200) {
var response = request.responseText.split(“|”);
document.getElementById(“order”).value = response[0];
document.getElementById(“address”).innerHTML =
response[1].replace(/n/g, “<br />”);
} else if (request.status == 404) {
alert (“Requested URL is not found.”);
} else if (request.status == 403) {
alert(“Access denied.”);
} else
alert(“status is ” + request.status);
}
}
虽然这依然相当简单,但是它的确多提供了一些有用的信息。图6给出了与图5相同的错误,但是这一次错误处理代码向用户或程序员更好地说明了究竟发生了什么。
图6. 特殊错误处理
在我们自己的应用程序中,可以考虑在发生认证失败的情况时清除用户名和密码,并向屏幕上添加一条错误消息。我们可以使用类似的方法来更好地处理找不到脚本或其他400类型的错误(例如405表示不允许使用诸如发送HEAD请求之类不可接受的请求方法,而407则表示需要进行代理认证)。然而不管采用哪种选择,都需要从对服务器上返回的状态代码开始入手进行处理。
其他请求类型
如果您真希望控制XMLHttpRequest对象,可以考虑最后实现这种功能 —— 将HEAD请求添加到指令中。在前两篇文章中,我们已经介绍了如何生成GET请求;在马上就要发表的一篇文章中,您会学习有关使用POST请求将数据发送到服务器上的知识。不过本着增强错误处理和信息搜集的精神,您应该学习如何生成HEAD请求。
生成请求
实际上生成HEAD请求非常简单;您可以使用”HEAD”(而不是”GET”或”POST”)作为第一个参数来调用open()方法,如清单9所示。
清单9. 使用Ajax生成一个HEAD请求
function getSalesData() {
createRequest();
var url = “/boards/servlet/updateBoardSales”;
request.open(“HEAD”, url, true);
request.onreadystatechange = updatePage;
request.send(null);
}
当您这样生成一个HEAD请求时,服务器并不会像对GET或POST请求一样返回一个真正的响应。相反,服务器只会返回资源的头(header),这包括响应中内容最后修改的时间、请求资源是否存在和很多其他有用信息。您可以在服务器处理并返回资源之前使用这些信息来了解有关资源的信息。
对于这种请求您可以做的最简单的事情就是简单地输出所有的响应头的内容。这可以让您了解通过HEAD请求可以使用什么。清单10提供了一个简单的回调函数,用来输出从HEAD请求中获得的响应头的内容。
清单10. 输出从HEAD请求中获得的响应头的内容
function updatePage() {
if (request.readyState == 4) {
alert(request.getAllResponseHeaders());
}
}
请参见图7,其中显示了从一个向服务器发出的HEAD请求的简单Ajax应用程序返回的响应头。
图7. HEAD请求的响应头
您可以单独使用这些头(从服务器类型到内容类型)在Ajax应用程序中提供其他信息或功能。
检查URL
您已经看到了当URL不存在时应该如何检查404错误。如果这变成一个常见的问题——可能是缺少了一个特定的脚本或servlet——那么您就可能会希望在生成完整的GET或POST请求之前来检查这个URL。要实现这种功能,生成一个HEAD请求,然后在回调函数中检查404错误;清单11给出了一个简单的回调函数。
清单11. 检查某个URL是否存在
function updatePage() {
if (request.readyState == 4) {
if (request.status == 200) {
alert(“URL exists”);
} else if (request.status == 404) {
alert(“URL does not exist.”);
} else {
alert(“Status is: ” + request.status);
}
}
}
诚实地说,这段代码的价值并不太大。服务器必须对请求进行响应,并构造一个响应来填充内容长度的响应头,因此并不能节省任何处理时间。另外,这花费的时间与生成请求并使用HEAD请求来查看URL是否存在所需要的时间一样多,因为它要生成使用GET或POST的请求,而不仅仅是如清单7所示一样来处理错误代码。不过,有时确切地了解目前什么可用也是非常有用的;您永远不会知道何时创造力就会迸发或者何时需要HEAD请求!
有用的HEAD请求
您会发现HEAD请求非常有用的一个领域是用来查看内容的长度或内容的类型。这样可以确定是否需要发回大量数据来处理请求,和服务器是否试图返回二进制数据,而不是HTML、文本或XML(在JavaScript中,这3种类型的数据都比二进制数据更容易处理)。
在这些情况中,您只使用了适当的头名,并将其传递给XMLHttpRequest对象的getResponseHeader()方法。因此要获取响应的长度,只需要调用request.getResponseHeader(“Content-Length”);。要获取内容类型,请使用request.getResponseHeader(“Content-Type”);。
在很多应用程序中,生成HEAD请求并没有增加任何功能,甚至可能会导致请求速度变慢(通过强制生成一个HEAD请求来获取有关响应的数据,然后在使用一个GET或POST请求来真正获取响应)。然而,在出现您不确定有关脚本或服务器端组件的情况时,使用HEAD请求可以获取一些基本的数据,而不需要对响应数据真正进行处理,也不需要大量的带宽来发送响应。
结束语
对于很多Ajax和Web程序员来说,本文中介绍的内容似乎是太高级了。生成HEAD请求的价值是什么呢?到底在什么情况下需要在JavaScript中显式地处理重定向状态代码呢?这些都是很好的问题;对于简单的应用程序来说,答案是这些高级技术的价值并不是非常大。
然而,Web已经不再是只需实现简单应用程序的地方了;用户已经变得更加高级,客户期望能够获得更好的稳定性、更高级的错误报告,如果应用程序有1%的时间停机,那么经理就可能会因此而被解雇。
因此您的工作就不能仅仅局限于简单的应用程序了,而是需要更深入理解XMLHttpRequest。
·如果您可以考虑各种就绪状态——并且理解了这些就绪状态在不同浏览器之间的区别——就可以快速调试应用程序了。您甚至可以基于就绪状态而开发一些创造性的功能,并向用户和客户回报请求的状态。
·如果您要对状态代码进行控制,就可以设置应用程序来处理脚本错误、非预期的响应以及边缘情况。结果是应用程序在所有的时间都可以正常工作,而不仅仅是只能一切都正常的情况下才能运行。
·增加这种生成HEAD请求的能力,检查某个URL是否存在,以及确认某个文件是否被修改过,这样就可以确保用户可以获得有效的页面,用户所看到的信息都是最新的,(最重要的是)让他们惊讶这个应用程序是如何健壮和通用。
本文的目的并非是要让您的应用程序显得十分华丽,而是帮助您去掉黄色聚光灯后重点昭显文字的美丽,或者外观更像桌面一样。尽管这些都是Ajax的功能(在后续几篇文章中就会介绍),不过它们却像是蛋糕表面的一层奶油。如果您可以使用Ajax来构建一个坚实的基础,让应用程序可以很好地处理错误和问题,用户就会返回您的站点和应用程序。(认真地说,您一定不希望错过下一篇文章!)
我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。
我原创,你原创,我们的内容世界才会更加精彩!
【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】
微信公众号
TechTarget
官方微博
TechTarget中国
相关推荐
-
八个超实用的jQuery技巧攻略
jQuery是JavaScript最好的库之一,主要用于制作动画、事件处理,支持Ajax及HTML脚本客户端。文中分享了8个超实用的jQuery代码技巧攻略,希望你会喜欢。
-
用于.NET的可移植HTTP客户端
关于在.NET、Silverlight、Windows Phone和Windows Store之间分享代码的问题之一,依旧是缺少发起HTTP请求的能力。
-
HTML5强大功能背后的安全陷阱
尽管HTML5使网站的功能更为强大,但开发人员需充分利用其新的技术特征来提高网站的安全性,使用不当会带安全问题,你知道吗?
-
前端页面开发之Node.js初学者指南
Node.js是刚刚兴起的一个概念,你对它的了解有多少?Node.js的意义是什么,它是怎么发展起来的?Node.js的作用是怎样的呢?