偶尔使用 Ajax 的开发人员也会注意到 Ajax 中的 x 并意识到它代表 XML。XML 是编程中最常用的数据格式之一,对于异步应用程序中的服务器响应能够带来切实的好处。在本文中,您将看到服务器如何在请求响应中发送 XML。
现在如果不使用 XML 就不能进行任何有意义的编程。无论考虑转向 XHTML 的网页设计人员、使用 JavaScript 的 Web 程序员、使用部署描述文件和数据绑定的服务器端程序员,还是研究基于 XML 的数据库的后端开发人员,都在使用这种可扩展标记语言。因此,XML 被认为是 Ajax 底层的核心技术之一就不足为奇了。
但是,这种观点反映到 Ajax 应用程序就表现在其核心对象所选的名称 —— XMLHttpRequest,这个名称不是很好,因为它并没有反映技术上的实际情况。换句话说,多数人之所以认为 XML 是 Ajax 的核心组成部分,仅仅是因为他们想当然地以为 XMLHttpRequest 对象在任何时候都使用 XML。但实情并非如此,本文第一部分给出了原因。实际上,您将看到在多数 Ajax 应用程序中 XML 很少出现。
XML 确实有应用在 Ajax 中,而且 XMLHttpRequest 也支持这种用法。也确实没有什么能阻挡您向服务器发送 XML。在本系列前面的文章中,我们使用普通文本和名/值参数发送数据,但 XML 也是一种可行的格式。本文将介绍如何来这样做。但最重要的是,我将讨论为何可以使用 XML 作为请求格式,以及为何在多数情况下不应该使用它。
XML:到底用没用?
对 Ajax 应用程序及它们使用 XML 的情况很容易犯想当然的错误:这种技术的名称(Ajax)及其使用的核心对象(XMLHttpRequest)都暗示了 XML 的使用,谈到 Ajax 应用程序的时候也经常听到 XML。但是,这种观点大错特错,如果希望在编写异步应用程序时真正做到胸有成竹,必须知道这种想法是错误的,而且最好知道为什么错误。
XMLHttpRequest:糟糕的名称和 HTTP
一项技术可能遇到的最糟的境况之一是它变得太炙手可热以至于无法再改变它的一些基本内容。XMLHttpRequest 恰恰是这种情形,它是 Ajax 应用程序中使用的基本对象。听起来它似乎是为通过 HTTP 请求发送 XML 或者以某种 XML 格式发出 HTTP 请求而设计的。但不论这个对象的名称听起来像什么,实际上它要做的只不过是为客户机代码(在网页中通常是 JavaScript)提供一种发送 HTTP 请求的方式。仅此而已,别无其他。
因此,如果将 XMLHttpRequest 改成某种更准确的名称可能更好一些,比如 HttpRequest,或者简简单单的 Request。但是,现在成千上万的人在应用程序中使用了 Ajax,而且我们知道需要几年时间(如果不是十几年的话)大部分用户才会改用 Internet Explorer 7.0 或 Firefox 1.5 这些新版本的浏览器,因此这么修改实际上是不可行的。最终我们不得不使用 XMLHttpRequest,这就要求开发人员要知道其名不符实的这一事实。
在一定程度上讲,对于不支持 XMLHttpRequest 的浏览器(特别是在 Windows 上)的最佳回溯方法之一就是使用 Microsoft IFRAME 对象。听起来可不像是 XML、HTTP 或请求,是不是?当然,所有这些都可能涉及到,但是这正清楚地说明了一点 —— XMLHttpRequest 对象更多的是关于在不重新加载页面的情况发出请求,而不会太多地涉及 XML 甚至 HTTP。
请求是 HTTP 而非 XML
另一种常见的错误是认为 XML 在幕后使用 —— 坦白地说,我也曾这么认为!但是,持这种观点表明您对这种技术还不甚了解。当用户打开浏览器从服务器上请求网页时,会输入 http://www.google.com 或者 http://www.headfirstlabs.com 这样的东西。即便不输入 http://,浏览器也会在地址栏的这部分加上。第一部分,即 http://,是关于如何通信的很直观的线索:通过超文本传输协议 HTTP。在网页中编写代码与服务器通信时,无论使用 Ajax 还是普通的表单 POST,甚至超链接,打交道的都是 HTTP。
既然浏览器和服务器之间的所有 Web 通信都通过 HTTP 进行,认为 XML 是 XMLHttpRequest 幕后所用的某种传输技术的想法就毫无道理了。当然在 HTTP 请求中可以发送 XML,但是 HTTP 是一个精确定义的协议,短时间内不可能消失。除了在请求中明确使用 XML,或者服务器用 XML 发送响应之外,XMLHttpRequest 对象使用的只是普普通通的 HTTP。因此,当再有人对您说 “哦,称为 XMLHttpRequest 是因为在幕后使用 XML” 的时候,您最好一笑了之,并耐心地解释什么是 HTTP,告诉他们虽然 XML 可以通过 HTTP 发送,但 XML 是一种数据格式而不是传输协议。通过这样的讨论,加深对它的理解。
使用 XML(真正)
到目前为止,我说的只是 Ajax 在哪些地方不使用 XML。但 Ajax 中的 x 和 XMLHttpRequest 中的 XML 仍然有其实际意义,在 Web 应用程序中使用 XML 有多种选择。这一节将讨论基本的选择,剩下的部分再深入探讨细节问题。
XML 选项
在异步应用程序中 XML 有两种基本的用法:
其中第一种用法,即用 XML 发送请求,需要将请求的格式设置为 XML,可以使用 API 来完成,也可以与文本连成字符串,然后将结果发送到服务器。按照这种思路,主要的任务就是通过既符合 XML 规则又能被服务器理解的方式构造请求。因此这里的关键实际上是 XML 格式,得到需要发送的数据之后,只需要用 XML 语法将其包装起来。本文后面讨论 XML 在 Ajax 应用程序中的这种用法。
第二种用法,即用 XML 接收请求,需要从服务器上接收响应,然后从 XML 提取数据(同样,可以用 API 或者采用蛮力方法)。这种情况下,关键在于来自服务器的数据,而您恰好需要从 XML 中提取这些数据以便使用。这是本系列下一期文章的主题,到那时候我们再详加讨论。
一点忠告
再详细讨论使用 XML 的细节之前,首先给您一句忠告:XML 不是一种简洁、快速和节省空间的格式。在后面几节以及本系列的下一期文章中将看到,在上下文中使用 XML 确实有一些很好的理由,XML 与普通文本的请求和响应(特别是响应)相比也确实有一些长处。但是,和普通文本相比,XML 通常总会占用更多的空间,速度也更慢,因为需要在消息中增加 XML 所需要的标签和语义。
如果需要编写速度很快、看起来像桌面应用的程序,XML 可能不是最佳选择。如果从普通文本开始,然后发现确实需要 XML,那么就使用它;但是如果从一开始就使用 XML,基本上可以肯定一定会降低应用程序的响应性。多数情况下,与将文本转化成下面这种 XML 相比,发送普通文本会更快一些(使用类似 name=jennifer 的名/值对):
<name>jennifer</name> |
看看哪些地方使 XML 增加了处理时间:将文本包装成 XML;发送额外信息(要注意我没有包含任何包围元素、XML 头或者可能出现在实际请求中的其他任何内容);让服务器解析 XML、生成响应、用 XML 包装响应,并将它发送回网页;让网页解析响应,最后使用它。因此要清楚什么时候使用 XML,不要一开始就认为它在很多情况下都能够加快应用程序;但,它可以增强灵活性,这就是我们现在要讨论的。
从客户机到服务器的 XML
我们来看看将 XML 作为从客户机向服务器发送数据的格式。我们首先讨论技术上的实现,然后花些时间分析什么时候适合什么时候不适合使用它。
发送名/值对
在您编写的 90% Web 应用程序中,最终都会使用名/值对发送到服务器。比方说,如果用户在网页表单中输入姓名和地址,可能希望数据采用下列形式:
firstName=Larry lastName=Gullahorn street=9018 Heatherhorn Drive city=Rowlett state=Texas zipCode=75080 |
如果使用普通文本把这些数据发送到服务器,可以使用清单 1 所示的代码。类似于本系列第一期文章中使用的那个例子。请参阅参考资料。
清单 1. 使用普通文本发送名/值对
function callServer() { // Get the city and state from the Web form var firstName = document.getElementById("firstName").value; var lastName = document.getElementById("lastName").value; var street = document.getElementById("street").value; var city = document.getElementById("city").value; var state = document.getElementById("state").value; var zipCode = document.getElementById("zipCode").value; // Build the URL to connect to var url = "/scripts/saveAddress.php?firstName=" + escape(firstName) + "&lastName=" + escape(lastName) + "&street=" + escape(street) + "&city=" + escape(city) + "&state=" + escape(state) + "&zipCode=" + escape(zipCode); // Open a connection to the server xmlHttp.open("GET", url, true); // Set up a function for the server to run when it's done xmlHttp.onreadystatechange = confirmUpdate; // Send the request xmlHttp.send(null); } |
将名/值对转化成 XML
如果希望这样使用 XML 作为数据格式,首先要做的是找到一种基本 XML 格式来存储数据。显然,名/值对可以全部转化成 XML 元素,以其中的名称作为元素名,值作为元素的内容:
<firstName>Larry</firstName> <lastName>Gullahorn</lastName> <street>9018 Heatherhorn Drive</street> <city>Rowlett</city> <state>Texas</state> <zipCode>75080</zipCode> |
当然,XML 要求有一个根元素;如果使用文档片段(XML 文档的一部分)的话则需要一个封闭元素。因此可能需要将上述 XML 转化成下面的形式:
<address> <firstName>Larry</firstName> <lastName>Gullahorn</lastName> <street>9018 Heatherhorn Drive</street> <city>Rowlett</city> <state>Texas</state> <zipCode>75080</zipCode> </address> |
现在基本上可以准备在 Web 客户机上创建这种结构并发送到服务器了。
通信,口头上的
在网络上传输 XML 之前,需要保证服务器以及发送数据的脚本能够接受 XML。现在对很多人来说这么强调似乎有点多余,认为这是理所当然的,但是很多新手往往认为只要通过网络发送 XML,就能够被正确地接收和解释。
实际上,需要两个步骤来保证发送的 XML 的数据能够被正确地接收:
1. 保证向其发送 XML 的脚本能够接受 XML 数据格式。
2. 保证脚本认可发送数据所采用的特定 XML 格式和结构。
这两方面都可能要求您进行人际沟通,必须明确地告知对方!严格地说,如果确实需要发送 XML 数据,多数脚本作者都会帮助您,因此寻找能够接受 XML 的脚本应该不难。但是,仍然需要保证格式是脚本所希望的格式。比方说,假设服务器接受下列格式的数据:
<profile> <firstName>Larry</firstName> <lastName>Gullahorn</lastName> <street>9018 Heatherhorn Drive</street> <city>Rowlett</city> <state>Texas</state> <zip-code>75080</zip-code> </profile> |
看起来和上面的 XML 类似,只有两点不同:
1. 来自客户机的 XML 封装在 address 元素,但是服务器要求数据封装在 profile 元素中。
2. 来自客户机的 XML 使用了 zipCode 元素,而服务器希望邮政编码放在 zip-code 元素中。
从大的层面上来说,这些小问题仅仅是服务器接收和处理数据的区别,但是服务器会彻底失败,在网页上(可能向其用户)显示意义含糊的错误消息。因此必须明确服务器的期望的格式,并把要发送的数据塞进那种格式。然后,只有在这时才会涉及到从客户机向服务器发送 XML 数据的真正的技术问题。
向服务器发送 XML
当向服务器发送 XML 的时候,更多的代码用于获取数据和包装成 XML,而不是真正的传输数据。实际上,只要准备好发送到服务器的 XML 字符串,发送工作就和普通文本一样了,如清单 2 所示。
清单 2. 用 XML 发送名/值对
function callServer() { // Get the city and state from the Web form var firstName = document.getElementById("firstName").value; var lastName = document.getElementById("lastName").value; var street = document.getElementById("street").value; var city = document.getElementById("city").value; var state = document.getElementById("state").value; var zipCode = document.getElementById("zipCode").value; var xmlString = "<profile>" + " <firstName>" + escape(firstName) + "</firstName>" + " <lastName>" + escape(lastName) + "</lastName>" + " <street>" + escape(street) + "</street>" + " <city>" + escape(city) + "</city>" + " <state>" + escape(state) + "</state>" + " <zip-code>" + escape(zipCode) + "</zip-code>" + "</profile>"; // Build the URL to connect to var url = "/scripts/saveAddress.php"; // Open a connection to the server xmlHttp.open("POST", url, true); // Tell the server you're sending it XML xmlHttp.setRequestHeader("Content-Type", "text/xml"); // Set up a function for the server to run when it's done xmlHttp.onreadystatechange = confirmUpdate; // Send the request xmlHttp.send(xmlString); } |
大部分代码都很简单,只有少数地方值得提一下。首先,请求中的数据必须手工格式化为 XML。阅读了三篇关于使用文档对象类型的文章之后,再来讨论它是不是很简单了?虽然不禁止在 JavaScript 中使用 DOM 创建 XML 文档,但是在通过 GET 或 POST 请求发送到网络上之前必须将 DOM 对象转化成文本。因此使用常规字符串操作来格式化数据更简单一些。当然,这样很容易出现错误和误输入,因此在编写处理 XML 的代码时必须非常小心。
建立 XML 之后,按照和发送文本基本相同的方式打开连接。对于 XML 最好使用 POST 请求,因为有些浏览器限制了 GET 请求字符串的长度,而 XML 可能很长,可以看到清单 2 中把 GET 改成了 POST 方法。此外,XML 通过 send() 方法发送,而不是附加在请求 URL 最后的参数。这些都是非常细微的区别,很容易修改。
但是必须编写一行新的代码:
xmlHttp.setRequestHeader("Content-Type", "text/xml"); |
看起来很难理解,它只不过是告诉服务器要发送的是 XML 而不是一般的名/值对。无论哪种情况,发送的数据都是文本,但这里使用 text/xml 或者 XML 作为普通文本发送。如果使用名/值对,对应的行应该是:
xmlHttp.setRequestHeader("Content-Type", "text/plain"); |
如果忘记告诉服务器发送的是 XML,就会出现问题,因此不要忘掉这一步骤。
完成这些之后,剩下的就是调用 send() 并传入 XML 字符串了。服务器将收到您的 XML 请求,并(假设已经做好了准备工作)接受 XML,解释它,然后返回响应。实际上要做的只有这么多 —— XML 请求只需要稍微修改代码。
发送 XML:好还是不好?
在结束 XML 响应的 XML 请求(以及本文)之前,我们花点时间讨论一下在请求中使用 XML 的感受。前面已经提到,就传输而言 XML 完全不是最快的方式,但是还有更多因素要考虑。
构造 XML 不是简单的事情
首先必须认识到,对于请求来说构造 XML 不是简单的事。如清单 2 所示,数据很快就会和 XML 语义纠缠在一起:
var xmlString = "<profile>" + " <firstName>" + escape(firstName) + "</firstName>" + " <lastName>" + escape(lastName) + "</lastName>" + " <street>" + escape(street) + "</street>" + " <city>" + escape(city) + "</city>" + " <state>" + escape(state) + "</state>" + " <zip-code>" + escape(zipCode) + "</zip-code>" + "</profile>"; |
似乎还不坏,但是要知道这是只有六个字段的 XML 片段。开发的多数 Web 表单都有十到十五个字段,虽然不一定所有的请求都使用 Ajax,但是应该考虑这种情况。至少要花和实际数据同样多的时间来处理尖括号和标签名称,有可能使本来很少的输入变得非常大。
这里的另一个问题前面已经提到,即必须手工创建 XML。使用 DOM 不是一种好的选择,因为没有简单易行的办法将 DOM 对象转化成在请求中发送的字符串。因此像这样使用字符串处理是最好的办法,不过也是一种维护起来最困难和新开发人员最难理解的方法。在这个例子中,所有 XML 都在一行中构造完成,如果分为多步只会更加混乱。
XML 没有为请求增加任何东西
除了复杂性的问题之外,和普通文本以及名/值对相比,在请求中使用 XML 实际上没有多少好处(如果有的话)。要注意,本文坚持使用前面用名/值对发送的同一些数据(请参阅清单 1)来用 XML 发送。我没有提什么数据能用 XML 但是不能用普通文本发送,这是因为实际上没有任何东西可用 XML 而不能用普通文本发送。
事实上这就是 XML 和请求的底线:不是一定非要这么做不可。在本系列的下一期文章中将看到服务器可以使用 XML 实现普通文本很难做到的一些事情,但请求不属于这种情况。因此除非和只接受 XML 的脚本(确实存在这样的脚本)打交道,在请求中最好使用普通文本。
结束语
通过本文,您现在可能已经开始对 Ajax 中的 XML 有一些更深的理解了。您知道 Ajax 应用程序不一定要使用 XML,XML 也不是数据传输中的什么法宝。还知道从网页向服务器发送 XML 不是多么难的事情。更重要的是,您知道为了确保服务器能够处理和响应请求需要做什么:必须保证服务器脚本接受 XML,而且能够识别用于发送数据的格式。
您还应该非常清楚 XML 对于请求来说并不一定是很好的数据格式。在以后的文章中,您将看到 XML 在某些情况下是有利的,但在多数请求中,它只会降低速度和增加复杂性。因此虽然通常我都会建议您马上应用在文章中学到的内容,但是对本文来说,我建议您在应用这里学到的知识时最好三思而后行。XML 请求在 Ajax 应用程序中有自己的价值,但是并不像您所想象的那么大。
在下一期文章中,我们将讨论服务器如何使用 XML 做出响应,以及 Web 应用程序如何处理这些响应。令人高兴的是,服务器能够将 XML 发送回 Web 应用程序,这样做的理由比较充分,因此那篇文章中的技术细节更实用,目前您只需要知道 XML 为何并非一定是最佳选择 —— 至少对发送请求而言。您可以尝试使用 XML 作为请求数据格式实现某些 Web 应用程序,然后再换回普通文本,看看哪种办法更快更简单。下一期文章再见。