ajax|web|web服务|脚本 本文先简单介绍一下可以用于 XML的 ECMAScript(ECMAScript for XML,E4X),它是一种对 JavaScript 的简单扩展,这使得编写 XML 脚本非常的简单。在本文中,作者演示了一种称为异步 JavaScript 及 XML(Asynchronous JavaScript and XML,AJAX)的 Web 编程模型,并且向您展示 JavaScript 的一些新 XML 扩展是如何使其更易于实现。 用于 XML 的 ECMAScript 您以前可能没有遇到过这个术语 ECMAScript。它实际上是 JavaScript 的正式名称。欧洲计算机制造商协会(European Computer Manufacturers Association,ECMA)是制定 JavaScript 标准的标准化组织(同时 C# 和CLR 标准也是该协会制定的)。ECMAScript 标准可以从 ECMA Web 站点免费获得。 E4X 是 JavaScript 的扩展,它实际上就是在 JavaScript 中添加了对 XML 的直接支持。它同时也是一种 ECMA 标准(查看参考资料——ECMA-357)。那么它对 XML 的直接支持是什么呢?为什么它具有很高的价值?如果您是一名 JavaScript 程序员,那么您可能已经使用了诸如 Netscape LiveConnect 或者 Rhino (一种运行在 Java™ 下的可以免费获得的 JavaScript 库)之类的技术来在您的 JavaScript 中使用这些 Java 库。这也就意味着您在 XML 库的帮助下已经可以创建、操作和使用 XML。同样地,如果您使用了 Microsoft® Internet Explorer,那么您已经通过 Microsoft MSXML2 库获得了 XML 的支持。这样的话,如果您已经使用了那些库文件,那么请做好进行重要变动的准备——E4X 会比它们更简单和容易。 在我们查看样本实例之前,对该试验需要注意:此时 E4X 有两种可用的实现方式。这两种可用的方式都是来自Mozilla。一种是浏览器使用的 C JavaScript 引擎(它是用代码来命名的 SpiderMonkey),并且在最新的 Mozilla 版本——我们使用 Mozilla 1.8a6 中是可用的。E4X 同样在 Rhino 中也是可用的。Rhino 是 Java 内置的 JavaScript 解释程序和编译程序,我们将展示其独立使用及运行在 Axis 内部的情景。两者都可以从 Mozilla 获得。 在这些样本实例中,我们首先从使用 Rhino 的命令行上的 E4X 开始,然后转向在使用 Mozilla 的浏览器内部使用 E4X,同时演示 AJAX 模型。在第二篇文章中,我们将向您展示如何通过将 Rhino 嵌入到 Apache Axis Web 服务引擎中来使用服务器内部的 E4X。但是在我们转向 Web 服务之前,先向您展示 E4X 中的 XML 编程基础。 一个简单样例 我们首先从一个简单的实例开始。我们首先对表示作者的相关信息的 XML 进行分析和操作。我们想得到的 XML 如下所示: 清单 1. 作者 XML 文档 <people> 如果我们以字符串的形式得到了该 XML 文档,那么就可以通过以下简单操作来“解析”该字符串: var x = new XML(peopleXMLString); 另外,我们也可以简单地将该 XML“嵌入”到代码中: var x = </person> 如果您同我们的操作完全一样,那么现在就可以尝试该操作。Rhino 1.6R1 最新的版本以及来自 Apache 的 XMLBeans 程序库都支持 E4X。使用它们非常的简单,只要获取这些包,将它们解压,然后将 js.jar 和 xbean.jar 添加到您的类路径中,就可以启动 JavaScript 处理程序了。 java -cp js.jar;xbean.jar 您现在可以尝试以下实例。您可以将它们从该处剪切并复制,或者将它们包含在 examples1.js 文件中,该文件位于 zip 文件(ws-ajax1code.zip)中,您可以通过点击本文顶部或者底部的代码图标来下载该文件。 您仅仅需要使用 JavaScript 的属性就可以查看 XML 的任何部分。例如: print(x.person[0].name); 您注意到了吗,到目前为止,我们还没有使用诸如 DOM 或者 SAX 之类的 XML API。XML 只是变成了一种 JavaScript 能够理解的本地类型。 使用以下代码来打印两个人的高度: for each (var h in x..height) { print(h) }; 语法非常有用。它返回所有的,任何深度的能够匹配后面紧跟的标记名称的子元素。因此 x..height 返回 height 标记的数值。 以下是另一个有用的语法: print(x.person.(name=="Ant").hair); 这将使得在 XML 树中可以很容易地进行查看操作。 一个更复杂的样本实例 假定您想要将高度值从米制度量单位更改为英美制度量单位(也就是英国人常说的英尺和英寸单位)。 下面首先是厘米到英寸的转换... function metricToImperial(cms) { 首先值得注意的一行是: var response = <height/>; 该行允许您将 XML “嵌入”到 JavaScript 中。采用这种语法有两个好处:第一,它确实会使 XML 的使用变得很容易。第二,这些代码的意义非常清晰。 现在您可以向该元素中添加更多的子元素: response.feet = ft; 这样创建了 <height> 子元素,其标记名称被设置为“feet”,并且将数值设置为变量 ft 的值。 您也可以用这种方式操作其属性: response.measure = "imperial"; 现在我们使用该功能来更新 XML: for each (var p in x.person) { print (x); 这里是输出内容: 如果您是一名 XML 高手,那么此时您可能想知道如何使用该语法来管理 XML 命名空间。这里有三种方式来实现该操作: 首先,您可以使用内嵌的 XML 语法: var soapMsg = <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"/>; 第二个方法是在创建元素之前设置缺省的 XML 命名空间: default xml namespace = new Namespace("http://www.w3.org/2003/05/soap-envelope"); 您可以将它设置为空字符串,以便对该缺省的命名空间进行重新设置,例如: default xml namespace = "" 最后一种方法是使用 :: 运算符
<person gender="male">
<name>Ant</name>
<hair>Shaggy</hair>
<eyes>Blue</eyes>
<height measure="metric">176</height>
</person>
<person gender="male">
<name>Paul</name>
<hair>Spiky</hair>
<eyes>Grey</eyes>
<height measure="metric">178</height>
</person>
</people>
<people>
<person gender="male">
<name>Ant</name>
<hair>Shaggy</hair>
<eyes>Blue</eyes>
<height measure="metric">176</height>
</person>
<person gender="male">
<name>Paul</name>
<hair>Spiky</hair>
<eyes>Grey</eyes>
<height measure="metric">178</height>
</people>;
org.mozilla.javascript.tools.shell.Main
Ant
print(x.person[1].hair);
Spiky
176
178
Shaggy
var totalinches = Math.round(cms/2.54);
var inch = totalinches%12;
var ft = (totalinches-inch)/12;
var response = <height/>;
response.feet = ft;
response.inches = inch + 2; // we sounded a bit short
response.measure = "imperial";
return response;
}
if (p.height.measure=="metric")
p.height=metricToImperial(p.height);
}
<person gender="male">
<name>Ant</name>
<hair>Shaggy</hair>
<eyes>Blue</eyes>
<height measure="imperial">
<feet>5</feet>
<inches>11</inches>
</height>
</person>
<person gender="male">
<name>Paul</name>
<hair>Spiky</hair>
<eyes>Grey</eyes>
<height measure="imperial">
<feet>5</feet>
<inches>12</inches>
</height>
</person>
</people>E4X 中的 XML 命名空间(Namespace)
print(soapMsg.namespace());
http://www.w3.org/2003/05/soap-envelope
soapMsg.Body.f::GetStockQuote="IBM";
print(soapMsg);
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Body>
<frem:GetStockQuote xmlns:frem="http://fremantle.org">
IBM
</frem:GetStockQuote>
</s:Body>
</s:Envelope>XML 元素排序
E4X 所具有的一个明显的优点是它完全支持 XML,包括排序功能。许多从 XML 文档到相应编程语言对象的直接映射仅仅支持符合常规对象语义的 XML 语义子集——因此就失去了不仅能够表示对象而且还表示文档的功能。但是对本文来说,它是不合适的,只要简单查看一下该规范就可以看出,E4X XML 对象具有的内置功能能够对 XML 元素进行细致准确的排序。
在 XML 中使用 Javascript 表达式
在转向 Web 服务之前,我们向您介绍的最后一个内容——使用大括号“{}”。以上我们介绍了“内嵌的”XML。E4X 也允许您重新进入 JavaScript 环境,并且可以包括了计算后的表达式值。例如:
var num = 36;
var p = <paul><age>{num}</age></paul>
print(p);
<paul>
<age>36</age>
</paul>
到目前为止,我们已经介绍了 E4X 的基础知识,下面我们就可以使用这些基本知识进行以下操作。
使用 E4X 来调用 Web 服务
本节中,我们将描述如何在以下两个环境中使用 E4X:
1. 使用 XMLHttpRequest 的 Mozilla 1.8
2. Java/Rhino
您可以很容易地在浏览器中使用 E4X 来调用 Web 服务。但是这里存在问题!到目前为止,唯一的支持 E4X 的浏览器是 Mozilla 1.8 的专业版。不过我们并没有将其作为一种可移植的跨浏览器的解决方案来推荐,以下的实例演示了 E4X 如何以一种简单的方式来调用 Web 服务。在下一节中,我们将查看运行在 Rhino JavaScript 引擎中的另一种方法。
AJAX
该样本实例显示了向 SOAP 服务器发送和接收 SOAP 消息的浏览器。为了实现该操作,我们通过 XMLHttpRequest 对象来使用 E4X 。这个对象非常的有用,而且 Mozilla 和 Internet Explorer 浏览器都支持它,它允许运行在浏览器内部的脚本语言在后台中生成 HTTP 请求。实际上,这就是为什么 Google 的 GMail 能做到任何事情的原因。这种架构最近被命为 Asynchronous JavaScript+XML(AJAX)。
从根本上来说,AJAX 的目的是要通过一种比标准 HTML 和 HTTP 的“页面”模型更为灵活的方式与服务器进行交互,以此来提高 Web 页面的响应能力和可用性。有关这方面一个很好的样本是 Google Maps beta,它明显地比以前的映射 Web 站点更具交互性。
令人鼓舞的是,根据新闻报道,结合了 E4X 的 AJAX 的性能更优!我们将向您展示浏览器应用的两种版本。第一个版本演示了交互操作,而第二个版本隐藏了 Web 页面上的按钮和内部的工作过程,以此来展示交互性和异步性。
浏览器的安全性
为了演示浏览器的安全性,我们使用了 xmethods.net 上可用的标准 Web 服务。但是,这里还存在一个问题,因为一般来说,浏览器的安全性规则不允许脚本程序或者 Java applet 程序创建网络连接,除非是与生成该页面的服务器建立网络连接。否则,您需要有一个监控页面,它将您的按键操作“复制”到另外一个服务器。
但是您可以避开这种操作。为实现该操作,您需要进行两步。首先,您必须启用 Mozilla 配置中的用于脚本程序的升级特权。(假定您已经下载并安装了 Mozilla 1.8 beta)。
在该浏览器的地址栏中输入 about:config,然后将 signed.applets.codebase_principal_support 的值从 false 更新为 true。(出于安全性考虑,请记得在完成全部操作后设回原值。)
然后在脚本程序中,您可以请求使用升级特权。当该脚本语言运行时,将会提示用户使用这些特权。代码行为:
netscape.security.PrivilegeManager.enablePrivilege( "UniversalXPConnect UniversalBrowserAccess");
股票报价客户端样本该脚本程序属于 stockclient.html 的一部分。如果您从本文下载了 ws-ajax1code.zip 文件,将其中的 zip 内容解压,然后使用 Mozilla 打开 stockclient.html,您将会看到如下内容:
图 1. Mozilla 中的 stockclient.html
为了对其进行验证,首先单击 Update URL。该操作通过使用 XMLHttpRequest 对象从 http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl(或者您在 WSDL 框中键入的任意 URL)获取 WSDL 文件,然后使用 E4X 从那里获取端口地址 URL。现在单击 Send,您将会看到 SOAP 请求被填写了。一两秒钟之后,此 SOAP 响应应该同结果字段一起被更新。我们查看一下这些代码。
股票报价客户端的脚本程序
该脚本程序带着对 IBM 的股票价格的 request 调用指定的URL。如果您正在使用 Axis 服务器,那么我们建议使用股票行情自动收录机符号 XXX,这是一种特殊的符号——部署的服务将总是返回一个固定的响应用于该收录机,而不是产生 Web 请求来得到真正的股票价格——因此使用该符号进行测试会好些。
您必须完成的第一个操作就是定义您希望使用的 E4X:
<script type="text/javascript;e4x=1">
当您按下 Send 按钮时,该脚本程序显示如下:
var s = new Namespace(
"s",
"http://schemas.xmlsoap.org/soap/envelope/");
var envelope = <s:Envelope xmlns:s={s}/>;
envelope.s::Body="";
var body = envelope.s::Body;
该操作对任何的 SOAP 请求都是通用的。它只是简单地创建了一个 SOAP 信封,而没有包括任何消息体。该操作的一个等价实现方式如下所示:
var envelope =
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body/>
</s:Envelope>
但是,前面的代码会容易些,而且也为您提供了指向主体元素的指针。
下一步是要创建消息的主体:
var x = new Namespace("x","urn:xmltoday-delayed-quotes");
body.x::getQuote = <x:getQuote xmlns:x={x}/>;
最后,你必须添加正确的符号:
var symbol = document.getElementById("symbol").value;var getQuote = body.x::getQuote;
getQuote.symbol=symbol;
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<x:getQuote xmlns:x="urn:xmethods-delayed-quotes">
<symbol>XXX</symbol>
</x:getQuote>
</s:Body>
</s:Envelope>
为了发送它,你必须使用 XMLHTTPRequest 对象。我们创建了一个简单的辅助函数,以支持使用 XMLHttpRequest 对象来调用使用 E4X 的服务。此 execService 函数不仅支持异步方式而且也支持同步方式。
function execService(url, xml, callback) {
var xmlhttp = new XMLHttpRequest();
var async=false;
if (arguments.length==3) async=true;
xmlhttp.open("POST", url, async);
xmlhttp.setRequestHeader("SOAPAction", "\"\"")
xmlhttp.setRequestHeader("Content-Type", "text/xml")
if (async) {
var f = function() {
if (xmlhttp.readyState==4) {
callback(new XML(xmlhttp.responseText));
}
}
xmlhttp.onreadystatechange = f;
}
xmlhttp.send(xml.toString());
if (!async) return new XML(xmlhttp.responseText);
}
下面我们来详细地看看这些代码。首先,这些代码支持两种调用方式。您可以使用其中一种:
XML execService(String url, XML envelope);或者void execService(String url, XML envelope, function callback);
在这种情况下,回调函数应该是 void callback(XML x)。
于是您可以使用该函数直接调用 XML 服务,并且等待响应,或者您也可以传送一个函数,使用 XML 响应消息调用该函数。
该函数基于参数的数量来决定它是异步还是同步(3 是异步),然后简单地使用 XMLHttpRequest 对象以 POST 请求方式来把 XML 消息放到 HTTP 请求信息,并把请求发送到指定的 URL。
我们设置了两个 HTTP 头信息——SOAPAction 和 Content-Type——然后使用 xmlhttp.send(xml.toString()) 将 SOAP 信封消息发送出去。
如果该调用行为是异步的,那么在调用从该响应创建的 XML 回调函数之前,它需要等待,直到 readyState 为 4(全部完成)。
因此使用该操作的代码如下所示:
var url = document.getElementById("url").value;
var callback = function(resp) {
alert(resp..*::getQuoteReturn);
}
execService(url, envelope, callback);
在我们的样例中,我们使用异步模型。典型的 Web 浏览器在与服务器通话时并不阻塞,并且我们也不希望它那样。举例来说,如果我们阻塞了浏览器,那么浏览器的窗口可能最终表现为“不响应”状态,从而促使用户结束它。
resp..*::getQuoteReturn 如果您是 E4X 新手,那么需要再次理解该语法。.. 意味着从树中搜索命名的元素。*:: 意味着任意的命名空间,因此该数值将是在响应 SOAP 封装的任意命名空间中被称为 getQuoteReturn 元素的数值。实际的样本代码 stockclient.html 同样也显示了该请求和响应 SOAP 封装。测试该样本——您应该看到如下内容:
图 2. 显示请求和响应 SOAP 封装的 stockclient.html
该 stockclient.html 看上去类似一个传统的使用提交按钮的 Web 页面,尽管它事实上并不是(浏览器一直在查看该页面,从未更改)。我们创建了该文件,以便您能理解交互行为。但是,真正的 AJAX 版本的页面要美观很多。此 stockclientAjax.html 没有按钮。随着您键入操作的进行,它会自动地更新股票价格。测试该样本。
虽然该页面没有按钮,但是只要您停止键入操作,它就能自动地做出请求(在做出请求之前,它需要等待 0.6 秒钟,以便在您停止的时候它能够“感应”到)。
生成来自 Rhino 的 Web 服务请求
Rhino 并不支持 XMLHTTPRequest 对象,但是这个问题您不必担心。因为 Rhino 运行在 Java 环境中,您可以使用 Java 功能来生成 Web 服务请求 。为演示该操作,我们编写了一个 XMLHttpRequest 对象的简单 Java 实现。Rhino 允许 Java 程序员发布 Java 语言来扩展它们的 JavaScript 环境。为了使用 Rhino shell 中的 XMLHttpRequest 对象,您只需确保 e4xutils.jar 在自己的类路径中,然后您就可以使用 shell 命令,defineClass,将其添加到自己的环境中:
>set classpath=.\js.jar;.\xbean.jar;.\e4xutils.jar;.
>java org.mozilla.javascript.tools.shell.Main
Rhino 1.6 release 1 2004 11 30H
js> defineClass('xmlhttp.XMLHttpRequest');
下面有个很简单的脚本程序对其进行测试:
>test.js
defineClass("xmlhttp.XMLHttpRequest");
var xh = new XMLHttpRequest();
xh.open("GET",
"http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl",
false);
xh.send(null);
var wsdl = new XML(xh.responseText);
print(wsdl..*::address.location);
>java org.mozilla.javascript.tools.shell.Main test.js
http://64.124.140.30:9090/soap
结果就是您现在可以使用您已经在 Mozilla 和 Rhino 中写入(E4X+XMLHttpRequest)的相同的脚本程序。
结束语
到目前为止,您已经看到了如何使用 E4X 和 Javascript 来初始化 Web 服务请求。
<