UML软件工程组织

 

 

在 Ajax 中进行 XML 处理,第 3 部分: 使用 JSON 并避免使用代理
 
2008-05-22 作者:Mark Pruett 出处:IBM
 
本文内容包括:
Ajax 风格的服务器调用不一定使用 XMLHttp 请求。本系列的最后一部分介绍天气面板的最后一种方法,利用 Web 公共服务 JavaScript Object Notation (JSON) 和动态脚本标记来实现。

前两部分介绍了三种方法创建可重用的天气面板。这些方法都采用了 Asynchronous JavaScript™ + XML (Ajax) 技术,特别是 JavaScript XMLHttpRequest 对象来实现天气面板库。这些方法都使用了某种形式的 Web 代理将 XML 天气数据从 National Weather Service (NWS) XML 搬到我的服务器上,以避免 Ajax 的同一域问题。

本系列的 第 1 部分 详细讨论了同一域 问题,出于安全上的考虑将 XMLHttp 请求限制到提供原始 Web 页面的同一台服务器上。实际上,很多 Ajax 应用程序都需要超出一台服务器范围之外的其他数据。因此设计的时候必须确定访问 Web 服务器配置(比如创建 Apache ProxyPass 规则)还是创建专门的服务器脚本。

实践证明,还有另一种办法。它避免了同一域问题,程序员不需要处理上面所述的任务。

方法 4 需要的工具

详细讨论天气面板库的最后一种实现之前,首先介绍一下所需的工具:

  • Yahoo! Pipes
  • JSON
  • 动态脚本标记

Yahoo! Pipes

Yahoo! Pipes 是一种基于 Web 的工具,可以聚合能够通过 Web 访问的不同类型的数据,比如 RSS 提要。Yahoo! Pipes 使用图形化编辑器(如 图 1)编辑器创建,该编辑器可在标准 Web 浏览器中运行,如 Windows® Internet Explorer® 或 Firefox。

图 1. Yahoo! Pipes 编辑器
Yahoo! Pipes 编辑器

可通过把模块从工具面板上拖拉布置到画布上来创建管道。模块输出到另一个模块输入之间的管道将模块联系起来。

完成的 Yahoo! Pipe 有一个惟一的 URL。访问该 URL 时,Pipe 逻辑在 Yahoo! 服务器上执行。服务器处理所有的数据访问,执行数据操作并输出结果。默认情况下数据以 RSS 格式输出,但是允许在 URL 中指定其他数据格式,包括 JSON。很快将看到能够以 JSON 格式输出数据这一点至关重要。

天气面板模块使用的 NWS 数据可作为 Yahoo! Pipes 的数据源。四字符 NWS 站点 ID 可作为 Yahoo! Pipe URL 的参数。

图 1 显示了最后一种天气面板实现所使用的管道。虽然 Yahoo! Pipes 能够完成非常复杂的数据处理功能,但这里只有一个目的:将 NWS 数据从 XML 转换成 JSON。

JSON

前面已经多次提到过 JSON。它是什么,我为何要在天气面板应用程序中使用呢?

JSON 即 JavaScript Object Notation,JavaScript 语言本身支持的一种数据格式。和需要 JavaScript 程序解析的 XML 不同,JSON 就是 JavaScript 代码。

作为 JSON 和 XML 的比较,首先看看弗吉尼亚州 Richmond NWS 数据的 XML 版本,如 清单 1 所示。

清单 1. XML 格式的 NWS 数据
 
                
<?xml version="1.0" encoding="ISO-8859-1"?> 
<current_observation version="1.0"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation=
 "http://www.weather.gov/data/current_obs/current_observation.xsd">

 <location>Richmond International Airport, VA</location>
 <station_id>KRIC</station_id>
 <observation_time>
 Last Updated on Jan 26, 1:54 pm EST
 </observation_time>
 <weather>Mostly Cloudy</weather>
 <temperature_string>41 F (5 C)</temperature_string>
 <relative_humidity>32</relative_humidity>
 <wind_string>Calm</wind_string>
 <visibility_mi>10.00</visibility_mi>
 <icon_url_base>
 http://weather.gov/weather/images/fcicons/
 </icon_url_base>
 <icon_url_name>bkn.jpg</icon_url_name>

</current_observation>

清单 2 是这些数据的 JSON 版本。

清单 2. 转化成 JSON 格式的 NWS 数据
 
                
{
 "count": 1,
 "value":
 {"title": "NOAA Regional Weather",
 "description": "Pipes Output",
 "link": "http:\/\/pipes.yahoo.com\/pipes\/pipe.info?_id=CI6HgSh43BGP8nmJouNLYQ",
 "callback": "",
 "items":
 [
 {
 "location": "Richmond International Airport, VA",
 "station_id": "KRIC",
 "observation_time": "Last Updated on Jan 26, 1:54 pm EST",
 "weather": "Mostly Cloudy",
 "temperature_string": "41 F (5 C)",
 "relative_humidity": "32",
 "wind_string": "Calm",
 "visibility_mi": "10.00",
 "icon_url_base": "http:\/\/weather.gov\/weather\/images\/fcicons\/",
 "icon_url_name": "bkn.jpg",
 }
 ]
 }
}

简单地说,JSON 就是一种 JavaScript 数据结构。上面清单中的 JSON 可以方便地赋给 JavaScript 变量然后引用,如 清单 3 所示。

清单 3. 在 JavaScript 代码中访问 JSON 数据结构
 
                
var weather = {
 "count": 1,
 "value":
 {
 "items":
 [
 {
 "location": "Richmond International Airport, VA",
 ...
 }
 ]
 }
 };

alert (weather.value.items[0].location);

上述 JavaScript 代码片段将缩减后的 JSON 数据赋给 JavaScript 变量 weather。location 元素作为 JavaScript alert() 函数的参数引用。地点数据显示在弹出的警告窗口中,如 图 2 所示。

图 2. 通过 JSON 显示在 alert() 中的地点
通过 JSON 显示在 alert() 中的地点

和访问等价的 XML DOM 结构相比,多数 JavaScript 程序员认为引用 JSON 数据结构更简单更直观。这也足以说明为何在 Ajax 应用程序中这种数据结构越来越普遍。

但是 JSON 吸引 Ajax 开发人员还有另一个原因:通过某种 JavaScript 技巧,JSON 可以避免代理数据。

动态脚本标记

前述三种天气面板库的实现方法有一个共同的特点。浏览器中运行的 JavaScript 程序必须(通过 XMLHttp 请求)从我自己的 服务器上请求数据。这是由于 XMLHttp 请求的同一域限制造成的。

此前有两种方法使用 Apache ProxyPass 规则来解决这个问题,还有一种使用专用的服务器端脚本从 NWS 服务器上获取数据。

但是如果不能改变服务器配置或者不能运行服务器端脚本怎么办呢?

有一种技术允许 JavaScript 程序访问其他域中服务器上的数据。这种技术有时候被称为脚本标记 hack

为了说明其工作原理,首先来看看 HTML 脚本标记。通常嵌入 Web 页面作为包含 JavaScript 代码库的一种办法,如 清单 4 所示。

清单 4. HTML script 标记
 
                
<html>
 <head>
 <title>An Example</title>
 <script language="JavaScript" src="some_javascript_code.js"></script>

清单 4 显示了一个 Web 页面中的前面几行。最后一行的脚本标记说明,将从服务器上加载名为 some_javascript_code.js 的 JavaScript 源文件。src 属性指定了脚本位置的 URL。该例中的脚本位于同一台服务器上,但并非必须如此。可以包含任务服务器上的 JavaScript 代码。

这就意味着使用脚本标记可把 JSON 数据加载到浏览器中,后者恰恰是 JavaScript 代码。对于天气面板应用程序来说似乎意义不大,因为脚本标记本身就是打开页面时加载的 HTML 的一部分。对于 Ajax 解决方案,需要在页面加载之后 发送服务器请求。

不过 JavaScript 代码可以操纵 Web 页面 DOM。可以添加和修改 Web 页面中的数据。如果在 JavaScript 代码中动态创建脚本标记并添加到 DOM 中会怎么样呢?结果如 清单 5 所示。

清单 5. 动态创建 script 标记
 
                
var head = document.getElementsByTagName("head").item(0);
var script = document.createElement ("script");
script.src = "some_javascript_code.js";
head.appendChild (script);

执行后将在当前页面的 head 标记中增加一个新的 script 标记。它相当于 清单 4 中的 HTML 片段,只不过 some_javascript_code.js 中的 JavaScript 代码是在页面加载之后又加载的。

方法 4:JSON 和动态脚本标记

您可能怀疑这对于天气面板库来说有什么价值。假设 some_javascript_code.js 文件的内容如下:

alert ("Hello!");

一旦 JavaScript 文件加载到浏览器中, JavaScript 解释器就会运行包含的任何可执行代码。因此会立刻显示一个警告窗口。

由于从 Yahoo! Pipes 生成的 JSON 数据是 JavaScript 代码,所以也可通过上述技术动态加载。但 Yahoo! Pipes JSON 数据不是可执行 JavaScript 代码,而仅仅是一种 JavaScript 数据结构。可以加载到浏览器,但是如何知道什么时候加载的?

在创建动态标记之前设置某种定时器,然后假定 JSON 在某个时间,比如说 3 秒之后到达,这种方法初看起来似乎可行。从好的方面说这种方法不可靠,而且实践证明这是不必要的。

每个 Yahoo! Pipe 都有惟一的 URL。清单 6 显示了 NWS 管道的 URL。

清单 6. NWS Yahoo! Pipe URL
 
                
http://pipes.yahoo.com/pipes/pipe.run?
&_id=CI6HgSh43BGP8nmJouNLYQ
&textinput1=KRIC
&_run=1
&_render=json
&_callback=myfunction
            

为了更清楚的看到不同的参数,我将 URL 分成了几行。_id 参数标识了这是我使用的管道,textinput1 给出了 NWS 站点 ID。_run 参数说明希望执行该管道,_render 告诉 Yahoo! Pipes 服务器以 JSON 格式输出数据。

最后一个参数 _callback 做什么呢?它告诉 Yahoo! 服务器将数据封装到指定的函数调用中,即 myfunction()。Yahoo! Pipes 返回 JavaScript 程序的结果如下:

myfunction ( {"count":1, ... } );

为了简化我省略了大部分 JSON 数据,但道理您应该清楚了。和前面加载包含 alert() 调用的 JavaScript 文件一样,JavaScript 解释器执行上述代码,调用 myfunction() 并把请求的全部数据传递给该函数调用。

利用回调函数完全不需要知道服务器什么时候完成 JSON 数据的加载。

那么回调函数中有什么?对于我的天气面板库,它完成天气数据的格式化,和方法 1 中的 XMLHttpRequest 响应函数非常类似。在方法 1 中,我从 responseXML DOM 对象中提取需要的数据。这里只需要访问 JSON 数据结构,如 清单 7 中的 jsonHandler 方法所示,这就是天气面板 JSON 方法的全部代码。

清单 7. 天气面板库的 JSON/Yahoo! Pipes 实现:weather_badge_ypipes_json.js
 
                
var yp = new Array();
var idx = 0;

function weather_badge (nws_id, div_name) {

 var callback_obj = "yp[" + idx + "]";

 var url = "http://pipes.yahoo.com/pipes/pipe.run?"
 + "&_id=CI6HgSh43BGP8nmJouNLYQ"
 + "&textinput1=" + nws_id
 + "&_run=1"
 + "&_render=json";

 yp[idx] = new YPipesWeather (url, div_name, callback_obj);
 yp[idx].requestJSON ();
 idx++;
}

// The YPipesWeather constructor

function YPipesWeather (ypipes_url, div_name, obj_name) {
 this.url = ypipes_url + "&_callback=" + obj_name + ".jsonHandler";
 this.div_name = div_name;
}

// The requestJSON method: it builds the script tag
// that launches our request to the Yahoo! server.

YPipesWeather.prototype.requestJSON = function () {

 // Dynamically create a script tag. This initiates
 // a request to the Yahoo! server.

 var head = document.getElementsByTagName("head").item(0);
 var script = document.createElement ("script");
 script.src = this.url;
 head.appendChild (script);
}

// The jsonHandler method: this is our callback function.
// It's called when the JSON is returned to our browser
// from Yahoo!.

YPipesWeather.prototype.jsonHandler = function (json) {
 var div = document.getElementById (this.div_name);

 var weather = json.value.items[0];

 var html = "";

 html += "<center>\n";

 html += "<b>" + weather.location + "</b><br>\n";

 html += weather.weather + "<br>";

 html += "<img border='0' src='"
 + weather.icon_url_base
 + weather.icon_url_name
 + "'><br>";

 html += weather.temperature_string + "<br>";

 html += "Wind: " + weather.wind_string + "<br>";
 html += "Humidity: " + weather.relative_humidity + "%<br>";
 html += "Visibility: " + weather.visibility_mi + " miles<br>";

 html += "<br><span style='font-size: 0.8em; font-weight: bold;'>"
 + weather.observation_time + "</span><br>";

 html += "</center>\n";

 div.innerHTML = html;

}

和前面的所有方法一样,也实现了 weather_badge() 函数。并向该函数传递四字符的 NWS 站点 ID 和 HTML DIV 标记名称。DIV 标记就是呈现天气面板的页面区域。

其他代码定义了名为 YPipesWeather 的 JavaScript 对象。该对象包括三个函数:构造器、动态创建脚本标记(从而运行 Yahoo! Pipe)的方法和一个回调函数。

结果是一个 JavaScript 库,虽然没有用 XMLHttpRequest 对象,但行为和前面三种方法相同。

图 3 显示了最后这种方法的数据管道。除了最开始打开页面,不需要再访问我的 Web 服务器。

图 3. JSON 和动态脚本标记
JSON 和动态脚本标记

清单 8 显示了利用天气面板库的 JSON 版本的示例 HTML Web 页面。

清单 8. 在 Web 页面中使用 JSON 天气面板库
 
                
<html>
 <head>
 <title>JSON Script Tag Example</title>
 <link rel="stylesheet" type="text/css" href="weather.css" />
 <script language="JavaScript" 
 src="weather_badge_ypipes_json.js">
 </script>
 <script>
 function init () {
 weather_badge ("KRIC", "target1");
 }
 </script>
 </head>

 <body onload="init();">
 <h3>JSON Script Tag Example</h3>

 <div class="wbadge" id="target1">
 Loading...
 </div>
 </body>
</html>

这四种方法的比较

回顾一下,我介绍了实现 Ajax 天气面板库的四种不同方法

  • 从 NWS 服务器读取 XML 数据
  • 解析 XML 数据提取需要的部分
  • 将提取的数据格式化为 HTML
  • 在 Web 页面中显示 HTML

表 1 描述了这四种方法。

表 1. 天气面板库的四种版本
 
方法 说明
1:遍历 DOM 树 服务器上的简单 Web 代理从 NWS 服务器获取数据并发送给浏览器。在浏览器中,JavaScript 解释器从返回的 responseXML DOM 树提取数据,设置 HTML 格式并插入 Web 页面的 DIV 标记。
2:服务器上的 XSLT 服务器端脚本从 NWS 服务器获取数据,使用 XSLT 将其转换成 HTML,然后将 HTML 片段返回浏览器。浏览器直接将其插入 DIV 标记。
3:客户端 XSLT 这种方法使用简单的 Web 代理(和方法 1 相同)将 XML 数据返回浏览器。和方法 1 不同的是,它利用 XSLT 将 XML 转换成 HTML 并插入 DIV 标记。
4:JSON 和动态脚本标记 使用外部服务(Yahoo! Pipes)将 NWS 数据从 XML 转换为 JSON。天气面板库利用专门的 JSON 和 JavaScript 语言把转换后的数据返回浏览器 — 不需要代理。

哪种方法最好?即便像这样一个简单的程序,答案也不是显而易见的。最佳解决方案有什么特点?

最后一种办法使用 JSON 避免了通过代理的需要。如果情况不允许修改服务器配置或者不能编写服务器脚本,这种办法实现起来最简单。但副作用是必须捆绑到第三方 Web 服务 Yahoo! Pipes。如果 Yahoo! 改变了服务,是否会破坏我的解决方案?

在服务器端将 XML 转换成 HTML 的方法 2 又如何呢?把大部分工作交给服务器是否合适?结论和您在多大程度上信赖 JavaScript 语言(跨越多种浏览器版本)有关。方法 3 说明了如何将大部分处理放到浏览器上。但是和能够控制的服务器脚本不同,用户使用的浏览器可能千差万别。而且如第 2 部分所述,不同的浏览器采用不同的方式完成 XSLT 处理这类任务。和后端服务器脚本相比,JavaScript 代码的调试更困难。除非所有浏览器上的 JavaScript 语言都是标准的,尽量减少 JavaScript 代码是一种谨慎的策略。

XSLT 又怎么样呢?使用 XSLT 抽象 XML 到 HTML 转换是否合理?方法 1 给出了一种替代方案,在 JavaScript 代码中访问 XML DOM 树;方法 4 给出了另一种方法,利用软件服务把 XML 转换成 JSON 然后访问 JSON 数据结构。使用 XSLT 很容易把转换从服务器转移到浏览器,反之亦然,不需要改变 XSLT 代码。XSLT 抽象层的代价是什么?我打赌方法 1 比方法 3 更快,因为前者直接访问大量已经解析的 XML 数据。浏览器端 XSLT 版本需要对这些 XML 数据运行 XSLT 处理程序 — 无疑需要更多的处理。

没有一种答案绝对正确,即便是天气面板这么简单的问题。可以客观的分析算法效率和执行速度,但实践中最终会遇到某些主观的问题。XSLT 抽象是否抵得上速度上的损失?对这类问题还需要考虑速度吗?在浏览器上运行更合理吗?可靠、简单和容易维护重要吗?

我使用哪种方法?

那么,如果我不是为了介绍这些技术来举天气面板的例子,我会用哪种方法呢?如果我能够肯定这不是一个实验性的解决方案,我会选择对方法 1 略加修改,即访问 XMLHttpRequest 返回的 XML DOM 树。如果这是很多面板应用程序中的第一个(可能还有用于股市图或者通用新闻提要显示的应用程序),我会选择更抽象的方法,比如那两种 XSLT 解决方案。如果希望别人在很多不同的服务器上使用面板,我将选择 JSON/Yahoo! Pipes 方法。如果这些需求互相交叉,我宁愿回到最初的解决方案。

下载

描述 名字 大小 下载方法
本系列的示例代码
x-xmlajax.zip
194KB

参考资料

学习 获得产品和技术
  • IBM 试用版软件:用这些试用版软件开发您的下一个项目,可直接从 developerWorks 下载。
讨论
 

组织简介 | 联系我们 |   Copyright 2002 ®  UML软件工程组织 京ICP备10020922号

京公海网安备110108001071号