XML:GWT 与 PHP 之间的桥梁

GWT 可以使用 PHP 服务,而 XML 提供方便的数据交换途径

Google Web Toolkit(GWT)应用程序除了以传统的 Java™ 方式连接到 servlet 外,还可以使用 PHP Web 服务发送和接收 XML 格式的数据。您将探索以 Java 和 PHP 语言生成和处理 XML 文档的方法。

Federico Kereki, 系统工程师, WSO2 Inc

Federico Kereki 的照片Federico Kereki 是一名来自乌拉圭的系统工程师,拥有超过 20 年的开发、咨询和大学教学的经验。目前,他正在与各种各样的缩略词打交道:SOA、GWT、Ajax、PHP,当然还有 FLOSS!



2009 年 6 月 01 日

通过 GWT 可以方便地访问用 Java 语言编写的服务器端 servlet,并且客户机和服务器之间的数据传递是透明的。但是,当使用 GWT 时,不仅可以与那些 servlet 通信,还可以随意地与所有类型的 Web 服务交换数据。在很多情况下(对于简单的服务),可以用纯文本格式传输数据,但是每当遇到结构化的数据或较复杂的数据(例如 RSS)时,数据很可能是用 XML 表示的。

常用缩写词

  • Ajax:Asynchronous JavaScript + XML
  • PEAR:PHP Extension and Application Repository
  • RPC:远程过程调用(Remote procedure call)
  • RSS:真正简单的聚合(Really Simple Syndication)
  • W3C:万维网联盟(World Wide Web Consortium)
  • XML:可扩展标记语言(Extensible Markup Language)

本文研究一个简单的 GWT 应用程序和两个 PHP Web 服务,展示生成和使用 XML 文档的几种不同的方法。本文无意成为详尽的教程或手册,而是提供一些忠告,使您能更快地开始使用 XML 作为连接 GWT 与 PHP 的桥梁。

测试应用程序

为了展示如何使用 XML 作为 PHP 与 GWT 之间的桥梁,我提供一个简单的应用程序,这个应用程序基于国家/地区/城市数据。 查看 清单 1 中的数据库创建代码,可以看到:

  • 国家(country)有惟一的代码(例如 UY 表示乌拉圭)和名称。
  • 国家 被划分为多个地区(region),地区有(国家内惟一的)代码和名称。
  • 地区 内有多个城市(city),城市有(纯 ASCII)名称、别名(可能包括外来字符)、人口(如果未知则为 0)、纬度和经度。城市名可出现在同一个国家的不同地区。

JSON:另一种可行的替代品

JavaScript Object Notation(JSON)最初是 JavaScript™ 语言的一部分,但是最终发展为 XML 的成熟、有效的替代品。JSON 提供一种简单、可读、基于文本的格式,用于表示数组和对象。而且,可以肯定地说,对于同样的数据,数据的 XML 表示和 JSON 表示在大小上相差无几。一些著名的站点(例如 Google 或 Yahoo!)同时提供 JSON 和 XML。

JSON 的优势在于,JavaScript 可以非常快地处理它(例如,它可以只用一条语句就将 JSON 转换成一个对象),这使得它很受 Web 开发人员的欢迎。由于 GWT 将所有客户端代码编译为 JavaScript 代码,显然 GWT 为它提供了一个很好的库,本文中的所有例子也可以用 JSON 而不是 XML 编写。请参阅 参考资料 小节,获得关于 JSON 的更多信息的链接。

清单 1. 数据库创建代码
CREATE DATABASE world
    DEFAULT CHARACTER SET latin1
    COLLATE latin1_general_ci;

USE world;

CREATE TABLE countries (
    countryCode char(2) NOT NULL,
    countryName varchar(50) NOT NULL,
    PRIMARY KEY (countryCode)
    KEY countryName (countryName)
    );

CREATE TABLE regions (
    countryCode char(2) NOT NULL,
    regionCode char(2) NOT NULL,
    regionName varchar(50) NOT NULL,
    PRIMARY KEY (countryCode,regionCode),
    KEY regionName (regionName)
    );

CREATE TABLE cities (
    countryCode char(2) NOT NULL,
    cityName varchar(50) NOT NULL,
    cityAccentedName varchar(50) NOT NULL,
    regionCode char(2) NOT NULL,
    population bigint(20) NOT NULL,
    latitude float(10,7) NOT NULL,
    longitude float(10,7) NOT NULL,
    KEY `INDEX` (countryCode,regionCode,cityName),
    KEY cityName (cityName),
    KEY cityAccentedName (cityAccentedName)
    );

我创建了一个简单的 GWT 项目,它只有一个表单和两个 PHP Web 服务。(下载 小节提供了完整的源代码)。启动该应用程序时,可以看到 图 1 中的窗口。

图 1. 空的表单
示例应用程序的空表单的截屏

GWT 表单允许输入一个城市名的一部分,然后调用一个 PHP 服务获得与输入的内容匹配的所有城市。这些城市显示在一个网格中,可以编辑人口(population)、纬度(latitude)和经度(longitude)字段。然后,可以将编辑的数据发回到另一个 PHP Web 服务,后者将更新数据库。所有数据传输都是通过 XML 进行的。2009 年是查尔斯达尔文 200 诞辰和他的著作物种起源 诞生 150 周年,您可以查看城市名中包含 DARWIN 的城市;图 2 显示了结果。

图 2. 搜索城市名中包含 “Darwin” 的城市
获得城市数据后的示例表单

一些额外的配置

下面是我使用的软件,仅供参考:

  • GWT version 1.5.3
  • PHP version 5.2.8
  • MySQL® database server version 5.0.67
  • Apache version 2.2.10 under OpenSUSE® version 11.1

我安装的所有软件都是开箱即用的,但是 GWT 要求一个额外的配置步骤,这样才能测试 GWT-PHP 连接;请参阅侧边栏 SOP 问题,看看是什么原因。为了禁用内部 GWT 浏览器的同源策略(same-origin policy,SOP)检查,可以编辑 GWT 目录中的 ./mozilla-1.7.12/greprefs/all.js 文件,在文件的最后添加 清单 2 中的代码行:

清单 2. 修改内部 GWT 浏览器的配置
pref("capability.policy.default.XMLHttpRequest.abort", "allAccess");
pref("capability.policy.default.XMLHttpRequest.getAllResponseHeaders","allAccess");
pref("capability.policy.default.XMLHttpRequest.getResponseHeader","allAccess");
pref("capability.policy.default.XMLHttpRequest.open", "allAccess");
pref("capability.policy.default.XMLHttpRequest.send", "allAccess");
pref("capability.policy.default.XMLHttpRequest.setRequestHeader","allAccess");
pref("capability.policy.default.XMLHttpRequest.onreadystatechange","allAccess");
pref("capability.policy.default.XMLHttpRequest.readyState", "allAccess");
pref("capability.policy.default.XMLHttpRequest.responseText","allAccess");
pref("capability.policy.default.XMLHttpRequest.responseXML","allAccess");
pref("capability.policy.default.XMLHttpRequest.status", "allAccess");
pref("capability.policy.default.XMLHttpRequest.statusText", "allAccess");

每当更新 GWT 时,需要再次作出这样的更改。而且,请注意,如果不这样做,编写的代码在 Hosted 模式下会失败,但是在 compiled 模式下却可以正确运行。作出以上更改后,您的代码可能在 Hosted 模式下可以运行,但是在 Compiled 模式下却会失败,所以要小心!

SOP 问题

SOP 是一种安全性约束,它实际上禁止从某个源(即 URL 的协议/主机/端口三元组)装载的页面访问来自不同源的数据。(Windows® Internet Explorer® 对待 SOP 问题非常随便,它会忽略端口的变化,但这并不是标准)。例如,如果 GWT Web 客户机是从 http://www.yoursite.com:80/some/page/at/your/site 装载的,那么 SOP 将不允许客户机获取来自该 URL 之外的数据,并阻塞对 https://www.yoursite.com(不同的协议)、http://othersite.com(不同的主机)甚至是 http://www.yoursite.com:81(不同的端口)的调用。

SOP 是一种很好的思想,因为它可以彻底防止来自某个源的恶意 JavaScript 代码访问和操纵从另一个源获得的数据。禁用 SOP 实际上是钓鱼者真正想要的:当您查看一个合法的、正当的页面时,却有一个第三方在监视它。如果有了 SOP,就可以确保您查看的所有内容都是由预期的源发送的;不可能存在来自其他(可能是可疑的)源的代码。

相反,对于 GWT 开发人员来说,SOP 相当麻烦。当在 Hosted 模式下测试应用程序时,它连接到端口 8888,但是要访问的 PHP 服务却在标准的 80 端口中,所以 SOP 将拒绝调用。(当然,在 Compiled 模式下部署应用程序之后,它可以完美地运行,因为它也是从标准的 80 端口运行的,刚好符合 SOP)。您不想访问所有类型的站点,只是想访问同一个源的不同端口,但是 SOP 就是拦着您。

用 PHP 发送 XML

这个应用程序只定义一个简单的表单,其中有一些标签、一个文本框、两个命令按钮和一个用于显示结果的网格。每当单击 Get cities 时,该应用程序调用一个 PHP 服务,以便获得一个 XML 文档,其中包含与文本框中输入的内容匹配的所有城市。清单 3 显示从 PHP 服务发送到 GWT 应用程序的一个示例 XML(有所简化)。生成的 XML 代码要用于演示一些 XML 功能,所以比用于真正的应用程序的 XML 要长得多。 通常,设计良好的 XML 服务使用更少的标记和更多的属性,避免缩进,并且更短一些。

清单 3. 搜索 “tokyo” 时生成的 XML 文档
<?xml version="1.0" encoding="UTF-8"?>
 <cities>
  <city name="tokyo">
   <country code="JP" name="Japan"/>
   <region code="40" name="Tokyo"/>
   <coords>
    <lat>35.6850014</lat>
    <lon>139.7513885</lon>
   </coords>
   <pop>31480498</pop>
  </city>
  <city name="tokyo">
   <country code="PG" name="Papua New Guinea"/>
    <region code="01" name="Central"/>
   <coords>
    <lat>-8.3999996</lat>
    <lon>147.1499939</lon>
   </coords>
  </city>
  <city name="tokyojitori">
   <country code="KR" name="Korea, Republic of"/>
   <region code="16" name="Cholla-namdo"/>
   <coords>
    <lat>34.2380562</lat>
    <lon>125.9394455</lon>
   </coords>
  </city>
</cities>

这个 PHP 服务本身有两个版本 — getcities1.php 和 getcities2.php —,每个版本展示生成 XML 的不同方法。

到目前为止,生成 XML 的最简单的方法是连续输出适当的文本,或者构建一个字符串,然后使用 echo 发出它。这里应该将 content type 设为 text/xml,使之能够被正确地识别,并且还要记得包括一个适当的 description 行,以指定 XML 版本和数据编码方式。另外还必须转义字符串,使字符串中不包括小于(<)、大于(>)或和(&)字符。最简单的方法是使用 htmlspecialchars() PHP 函数。代码很容易编写,如 清单 4 所示。注意,缩进和换行实际上是不需要的,不过这样做可以让代码更易于阅读。

清单 4. 从 PHP 服务生成 XML 的最简单的方法
...
header("Content-type: text/xml");
...
echo '<?xml version="1.0" encoding="UTF-8"?>'."\n";
echo '<cities>'."\n";
...
while ($row= mysql_fetch_assoc($result)) {
  echo ' <city name="'.htmlspecialchars($row['cityName']).'">'."\n";
  echo '  <country code="'.$row['countryCode'].'" ';
  echo 'name="'.htmlentities($row['countryName']).'"/>'."\n";
  echo '  <region code="'.$row['regionCode'].'" ';
  echo 'name="'.htmlentities($row['regionName']).'"/>'."\n";

  echo '  <coords>'."\n";
  echo '    <lat>'.$row['latitude'].'</lat>'."\n";
  echo '    <lon>'.$row['longitude'].'</lon>'."\n";
  echo '  </coords>'."\n";

  if ($row['population']>0) {
    echo '  <pop>'.$row['population'].'</pop>'."\n";
  }

  echo ' </city>'."\n";
}
echo '</cities>'."\n";

生成 XML 代码的另一种方法(也不是很长)是使用 XMLWriter。(与之成对的另一个类 XMLReader 可用于 XML 处理)。现在可以不用关心字符的转义,因为这是自动完成的。虽然这种方法看起来比前面的 echo() 方法冗长一点,但是这种方法编写的代码更易于理解。特别注意,这里使用了 php://output 协议,以便用 echo 命令发出文本。(如 清单 5 所示)。

清单 5. XMLWriter 提供逐个元素地构建 XML 文档的简单方法
...
$writer= new XMLWriter();
$writer->openURI('php://output')
$writer->startDocument('1.0', 'UTF-8');
$writer->startElement("cities");

while ($row= mysql_fetch_assoc($result)) {
  $writer->startElement("city");
  $writer->writeAttribute("name", $row['cityName']);

  $writer->startElement("country");
  $writer->writeAttribute("code", $row['countryCode']);
  $writer->writeAttribute("name", $row['countryName']);
  $writer->endElement();

  $writer->startElement("region");
  $writer->writeAttribute("code", $row['regionCode']);
  $writer->writeAttribute("name", $row['regionName']);
  $writer->endElement();

  $writer->startElement("coords");
  $writer->writeElement("lat", $row['latitude']);
  $writer->writeElement("lon", $row['longitude']);
  $writer->endElement();

  if ($row['population']>0) {
    $writer->writeElement("pop", $row['population']);
  }

  $writer->endElement();	// city
}
$writer->endElement(); // cities
...

如果想体验更多生成 XML 的方法,PHP 当然提供了很多的选择。例如,可以使用 SimpleXML;以后您将使用它读取 XML,但是它还提供 XML 文档创建功能。另外,还可以看一下 PEAR 框架,它包括一些可用于轻松生成 XML 的类。(请参阅 参考资料,获得更多信息的链接)。

用 GWT 处理 XML

GWT 只提供 XMLParser(在 com.google.gwt.xml.client 包中)用于读、写 XML。可以使用 parse() 方法创建一个 Document,然后使用 getDocumentElement() 获得它的根元素;然后,便可以开始处理 XML。

注意,应该使用 removeWhitespace() 方法去掉文档中的空白。浏览器解析器有时候会创建与制表符或换行符对应的空文本节点,如果不去掉它们,处理过程中会遇到不相关的、意外的元素,这样会破坏逻辑(见 清单 6)。另外还有一点要注意:如果预期有 CDATA 区段,那么需要检查浏览器是否接受 supportsCDATASection() 方法;如果不接受,那些区段将产生文本节点。请参阅 GWT 文档(见 参考资料),获得更多这方面的信息。

清单 6. GWT 中提供了 XMLParser 用于读取和创建 XML
protected void loadCities(final String xmlCities) {
  ...
  final Document xmlDoc= XMLParser.parse(xmlCities);
  final Element root= xmlDoc.getDocumentElement();
  XMLParser.removeWhitespace(xmlDoc);

  final NodeList cities= root.getElementsByTagName("city");
  for (int i= 0; i < cities.getLength(); i++) {
    final Element city= (Element)cities.item(i);
    // show city.getAttributeNode("name").getValue()

    final Element country= (Element)city.getElementsByTagName("country").item(0);
    // show country.getAttributeNode("code").getValue()
    // show country.getAttributeNode("name").getValue()
    ...
    final Element population= (Element)city.getElementsByTagName("pop").item(0);
    if (population != null) {
      // show population.getFirstChild().getNodeValue()
    }

    final Element coords= (Element)city.getElementsByTagName("coords").item(0);
    final Element lat= (Element)coords.getElementsByTagName("lat").item(0);
    // show lat.getFirstChild().getNodeValue()
    ...
  }
...

要获得重复的元素(例如这个例子中的 city),可以使用 getElementsByTagName() 方法,并迭代生成的数组。另一种方法是使用 getFirstChild() 方法,然后使用 getNextSibling() 遍历同一级别上的其他元素。要获得属性,首先需要使用 getAttributeNode() 方法,然后使用 getValue() 方法。还有一些用于处理 CDATA 区段、注释和所有可能的 XML 组件的方法。

用 GWT 发送 XML

GWT 应用程序让用户编辑人口、纬度和经度字段,然后将城市数据发回到服务器,以更新数据库。有两种算法:一种是简单的算法,这种算法逐块构建 XML 字符串,还有一种基于 XMLParser 的算法,该算法使用特定的方法创建所需的结构。

getCities1() 方法中显示了较简单的算法。可以使用 StringStringBuffer 对象构建 XML。我使用了前者,因为它使代码更加清晰;但是如果考虑性能,后者很可能更好一些。这里也存在和 PHP 版本中一样的字符串转义问题,所以使用 Html.htmlspecialchars() 方法修改该问题。(见 清单 7,为了看起来更清晰,稍微作了修改)。

清单 7. 使用字符串逐块构建 XML 很简单
protected String getCities1() {
  String result= "";

  result+= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
  result+= "<cities>\n";

  for (all rows in the grid) {
    // get cityName, countryCode, regionCode, pop, lat, and lon, from the grid

    result+= " <city name=\"" + Html.htmlspecialchars(cityName) + "\">\n";
    result+= "  <country code=\"" + countryCode + "\"/>\n";
    result+= "  <region code=\"" + regionCode + "\"/>\n";
    if (!pop.equals("0") && !pop.isEmpty()) {
      result+= "  <pop>" + pop + "</pop>\n";
    }

    result+= "  <coords>\n";
    result+= "   <lat>" + lat + "</lat>\n";
    result+= "   <lon>" + lon + "</lon>\n";
    result+= "  </coords>\n";
    result+= "</city>\n";
  }
  result+= "</cities>\n";
  return result;
}

另一种创建 XML 的简单算法是 XMLParser 类的 createDocument() 方法。这种算法的风格很容易让人想起 PHP 中的 SimpleXML 函数,首先创建一个空文档,然后向其中添加元素。可以创建所有类型的节点,并设置属性值。最后,标准的 Java toString() 方法生成对象的一个表示。您只需添加初始版本和编码行,就可以得到需要的字符串。(见 清单 8,为了看起来更清晰,代码有所修改和删减)。

清单 8. 创建 XML 的 XMLParser 方法类似于 PHP 中的 SimpleXML 方法
protected String getCities2() {
  Document xml= XMLParser.createDocument();
  Element cities= xml.createElement("cities");
  xml.appendChild(cities);

  for (all rows in the grid) {
    // get cityName, countryCode, regionCode, pop, lat, and lon, from the grid

    Element city= xml.createElement("city");
    city.setAttribute("name", cityName);

    Element country= xml.createElement("country");
    country.setAttribute("code", countryCode);
    city.appendChild(country);
    ...
    if (!pop.equals("0") && !pop.isEmpty()) {
      Element popEl= xml.createElement("pop");
      Text popText= xml.createTextNode(pop);
      popEl.appendChild(popText);
      city.appendChild(popEl);
    }

    Element coords= xml.createElement("coords");
    Element lat= xml.createElement("lat");
    Text latText= xml.createTextNode(lat);
    lat.appendChild(latText);
    coords.appendChild(lat);
    ...
    city.appendChild(coords);
    cities.appendChild(city);
  }
  return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + xml.toString();
}

在 PHP 中读取 XML

在 PHP 中处理 XML 是一个老问题,有很多的解决方案。但是,我认为,从清晰和简洁的角度看,没有哪种方案能比得上 SimpleXML 。基本上,首先通过将 XML 文档提供给 simplexml_load_string() 方法创建一个 PHP 对象,然后使用标准的 PHP 操作符遍历结果。属性变成数组中的元素(例如,清单 9 展示了如何读取国家代码),而元素则可以作为一个数组(通过使用 children() 方法)或作为对象的属性来访问。

清单 9. SimpleXML 是目前在 PHP 中进行 XML 处理的最容易的方法
$xml_str= $_POST["xmldata"];
$xml_obj= simplexml_load_string($xml_str);
...
foreach($xml_obj->children() as $city) {
  $name= addslashes($city['name']);
  $country= $city->country['code'];
  $region= $city->region['code'];
  $pop= $city->pop;
  $lat= $city->coords->lat;
  $lon= $city->coords->lon;

  mysql_query("REPLACE INTO cities ".
    "(cityName, countryCode, regionCode, population, latitude, longitude) VALUES (".
    "'{$name}', '{$country}', '{$region}', '{$pop}', '{$lat}', '{$lon}')");
}

在 PHP 中处理 XML 还有许多种方法,可以从 参考资料 小节找到相关的链接。

结束语

有很多方法可以使用 XML 作为 GWT 与 PHP 之间的桥梁,我只是略作探讨,不过,本文给出的方法应该足以让您迈出第一步。需要记住的要点是,GWT 不止局限于它自己的 RPC 方法,还可以与 XML 友好共存,轻松生成和使用 XML 文档。


下载

描述名字大小
本文的 Java 源代码java_source_code.zip4KB
本文的 PHP 源代码php_source_code.zip4KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Java technology, Open source, Web development
ArticleID=392863
ArticleTitle=XML:GWT 与 PHP 之间的桥梁
publish-date=06012009