XML как связующее звено между GWT и PHP

Приложения GWT могут вызывать PHP-сервисы, используя XML для простого обмена данными

Инструментарий Google для Web-разработчиков (Google Web Toolkit – GWT) позволяет приложениям вызывать не только старые, добрые Java™-сервлеты, но и Web-сервисы, написанные на PHP, используя XML в качестве формата для обмена данными. В этой статье описываются методы создания и обработки документов XML, причем как на языке Java, так и PHP.

Федерико Кереки, системный инженер, Независимый разработчик

Photo of Federico KerekiФедерико Кереки (Federico Kereki) – системный инженер из Уругвая, за плечами которого более 20 лет опыта системной разработки, консалтинга и преподавания в университетах. В настоящее время он работает с широким кругом технологий, в частности SOA, GWT, Ajax, PHP и, разумеется, FLOSS. Связаться с Федерико можно по адресу fkereki@gmail.com.



12.11.2010

Часто встречающиеся аббревиатуры

  • Ajax: асинхронный JavaScript и XML
  • PEAR: репозиторий расширений и приложений PHP
  • RPC: удаленный вызов процедур
  • RSS: технология простого распространения (синдицирования) информации
  • W3C: консорциум World Wide Web
  • XML: расширяемый язык разметки

GWT упрощает обращение к сервлетам, написанным на Java, обеспечивая прозрачный обмен данными между клиентом и сервером. Однако возможности GWT не ограничиваются взаимодействием с сервлетами, охватывая обмен данными с любыми типами Web-сервисов. Во многих случаях (особенно при работе с простыми сервисами) информация может передаваться в виде простого текста, однако при работе со структурированными данными (например, RSS) высока вероятность использования XML.

В этой статье рассматривается несколько различных способов формирования и обработки документов XML на примере взаимодействия приложения GWT и двух Web-сервисов, написанных на PHP. Статья отнюдь не претендует на роль подробного руководства или путеводителя по GWT. Вместо этого в ней содержится ряд советов, которые позволят вам быстрее начать работать с XML в качестве связующего звена между приложениями GWT и сервисами PHP.

Пробное приложение

JSON: эффективная альтернатива XML

Объектная нотация JavaScript™ (JSON) изначально являлась частью языка JavaScript, однако в итоге превратилась в отдельный полноценный язык, представляющий собой альтернативу XML. К преимуществам JSON можно отнести простое и удобочитаемое текстовое представление объектов и массивов. Представления одних и тех же данных средствами XML и JSON не сильно отличаются по размеру. В настоящее время сервисы некоторых популярных Web-сайтов, в частности Google и Yahoo!, предоставляют данные как в формате XML, так и JSON.

Преимуществом JSON по сравнению с XML является более простая работа с данными в JavaScript (например, для создания объекта из его представления в JSON достаточно одной строчки кода), что делает его привлекательным для разработчиков Web-приложений. Поскольку GWT компилирует всю клиентскую часть приложения в JavaScript, она также предоставляет мощные средства для работы с JSON. Как следствие, во всех примерах, приведенных в данной статье, можно было бы использовать JSON вместо XML. Ссылки на источники более подробной информации о JSON приведены в разделе Ресурсы.

В качестве примера использования XML для обмена данными между PHP и GWT мы рассмотрим простое приложение, работающее с географической информацией (городами, районами и странами). В листинге 1 приведен код создания базы данных, из которого можно сделать следующие выводы:

  • Каждая страна имеет название и уникальный код (например, UY является кодом Уругвая).
  • Страны делятся на регионы, которые идентифицируются собственным названием и кодом страны.
  • В регионах находятся города, характеризующиеся названием, состоящим только из ASCII-символов, названием, которое может включать не-ASCII символы, численностью населения (значение 0 говорит о том, что численность населения неизвестна), а также широтой и долготой. Одноименные города могут находиться в разных регионах одной страны.
Листинг 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-проект, включающий в себя одну форму и два Web-сервиса PHP. Запустив приложение, вы увидите окно, показанное на рисунке 1.

Рисунок 1. Пустая форма ввода
A screen capture of the empty form for the sample application

Форма GWT предлагает ввести часть названия города, а затем вызывает PHP-сервис, который вернет все города, чьи имена соответствуют введенной строке. Найденные города отображаются в виде таблицы с возможностью редактирования численности населения, широты и долготы. Измененные данные затем передаются второму Web-сервису PHP, который обновляет информацию о городе в базе данных. При этом вся информация между приложением и сервисами передается в формате XML. Вспомнив, что в 2009 г. исполняется 200 лет со дня рождения Чарльза Дарвина и 150 лет с момента выхода его книги "Происхождение видов", можно попробовать найти все города, название которых включает строку DARWIN. Результаты показаны на рисунке 2.

Рисунок 2. Результаты поиска городов, чьи названия включают строку "Darwin"
The sample form, after it gets the cities data

Конфигурирование приложения

При написании статьи использовались следующие версии программного обеспечения:

  • GWT 1.5.3
  • PHP 5.2.8
  • СУБД MySQL® 5.0.67
  • Apache 2.2.10 под операционной системой OpenSUSE® 11.1

Все перечисленные выше приложения не требовали дополнительных настроек, за исключением библиотеки GWT, которую пришлось конфигурировать для проверки тестирования соединений между GWT и PHP (описание этой необходимости приводится в заметке "Проблема SOP"). Для того чтобы отключить проверку "правила одного источника" (SOP) во внутреннем браузере GWT, отредактируйте файл ./mozilla-1.7.12/greprefs/all.js в каталоге GWT, добавив в конец строки из листинга 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");

Проблема SOP

Правило одного источника (same-origin policy – SOP) – это накладываемый из соображений безопасности запрет на доступ к данным со страниц, которые находятся на другом сайте (другими словами, если комбинация "протокол/сервер/порт" в их URL не совпадает с аналогичной комбинацией в адресе данных). При этом браузер Windows® Internet Explorer® не принимает во внимание разницу в номере порта, но такое поведение является нестандартным. В качестве примера рассмотрим следующую ситуацию: ваше клиентское Web-приложение GWT было загружено по адресу http://www.yoursite.com:80/some/page/at/your/site. При этом SOP не позволит приложению обратиться к данным по адресам https://www.yoursite.com (другой протокол), http://othersite.com (другой сервер) и даже http://www.yoursite.com:81 (другой номер порта).

Данное правило имеет смысл, поскольку оно не разрешает вредоносному JavaScript-коду обращаться к данным, находящимся по другому адресу в Интернет, и манипулировать ими. Отключение правила SOP было бы крайне выгодно злоумышленникам, поскольку его присутствие гарантирует, что информация, отображенная на странице, была получена от ожидаемого источника, а не от другого, потенциально небезопасного сервера в Интернете.

К сожалению, правило SOP зачастую вставляет палки в колеса программистам GWT. Соединения с приложениями, запущенными в режиме хоста, осуществляются через порт 8888, в то время как обращение к сервисам PHP должно идти через стандартный порт 80. В результате SOP блокирует такие вызовы (разумеется, после развертывания в скомпилированном виде приложение должно выполняться нормально, поскольку оно само будет располагаться по адресу с портом 80). При этом приложению совершенно необязательно обращаться к произвольным ресурсам в Интернете, вам всего лишь необходимо иметь доступ к сервисам, находящимся на том же сервере, но имеющим другой номер порта.

То же самое придется делать после каждого обновления GWT. Кроме того, обратите внимание, что если не сделать этих изменений, то ваше приложение может корректно функционировать в скомпилированном режиме, но аварийно завершаться в режиме хоста. Если же произвести изменения, то возможна обратная ситуация: корректное выполнение в режиме хоста и ошибка в скомпилированном виде, поэтому будьте внимательны!

Отправка данных в виде XML сервису на PHP

Пока приложение включает в себя только простую форму ввода с несколькими метками, текстовым полем, двумя кнопками и таблицей для отображения результатов. При нажатии на кнопку Найти города приложение отправит запрос PHP-сервису, который должен вернуть документ XML, содержащий все города, названия которых соответствуют строке, заданной в поле ввода. Сокращенный пример документа XML, передаваемого PHP-сервисом приложению, приведен в листинге 3. В данном фрагменте иллюстрируется ряд возможностей XML, поэтому он значительно длиннее, чем типичные XML-форматы, использующиеся в реальных приложениях. Как правило, хорошо спроектированные XML-сервисы используют меньшее число тегов и больше атрибутов, а также избегают отступов, что сокращает объем документов.

Листинг 3. Документ XML, возвращенный сервером при поиске всех городов, содержащих строку "tokyo"
<?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 and getcities2.php), демонстрирующие различные способы формирования документов XML.

Простейший способ выдачи результатов в виде XML заключается в последовательном формировании строки при помощи конкатенации и ее последующей отправке клиенту функцией echo. При этом следует задать text/xml в качестве типа содержимого, чтобы формат был корректно распознан, а также включить информацию о версии и типе кодировки в начало документа. Кроме того, необходимо позаботиться об экранировании символов '<', '>' и '&', для чего можно использовать функцию htmlspecialchars() в PHP. Как видно из листинга 4, данный метод очень прост в реализации. Учтите, отступы и переносы строк не являются обязательными, однако они улучшают читаемость кода.

Листинг 4. Простейший способ выдачи данных в формате XML из сервиса на PHP
...
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). В этом случае можно не беспокоиться об экранировании символов, поскольку это будет сделано автоматически. Возможно, данный метод покажется вам несколько громоздким, однако, по мнению ряда разработчиков, он делает код более понятным (листинг 5). В частности, обратите внимание на протокол php://output, который говорит о том, что содержимое документа будет отправлено клиенту, как и в предыдущем случае.

Листинг 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
...

PHP предоставляет множество возможностей для экспериментирования с различными способами формирования документов XML. Например, SimpleXML, который ниже будет использоваться для чтения XML, можно также применять для создания документов. Кроме того, имеет смысл обратить внимание на репозиторий PEAR, включающий несколько классов, упрощающих генерацию XML (ссылки на источники более подробной информации приведены в разделе "Ресурсы").

Обработка XML в GWT

GWT содержит класс com.google.gwt.xml.client.XMLParser, служащий как для чтения, так и для формирования документов XML. Для создания экземпляра Document сначала следует вызвать метод parse(), а затем получить ссылку на корневой элемент при помощи метода getDocumentElement(), после чего можно производить обход всех остальных узлов документа.

Необходимо помнить о необходимости вызова метода removeWhitespace()  для удаления пробелов и табуляций из исходного документа. Если этого не сделать, то XML-процессор может создавать на каждый символ пробела пустые текстовые узлы, которые могут оказаться сюрпризом для вашего класса-обработчика XML и нарушить логику его работы (листинг 6). Кроме того, если документ содержит секции CDATA, то необходимо убедиться, что браузер поддерживает метод supportsCDATASection(). Если нет, то содержимое этих секций будет представлено в виде текстовых элементов. Более подробная информация на эту тему приведена в документации GWT (см. раздел "Ресурсы").

Листинг 6. 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);
    // вывести city.getAttributeNode("name").getValue()

    final Element country= (Element)city.getElementsByTagName("country").item(0);
    // вывести country.getAttributeNode("code").getValue()
    // вывести country.getAttributeNode("name").getValue()
    ...
    final Element population= (Element)city.getElementsByTagName("pop").item(0);
    if (population != null) {
      // вывести population.getFirstChild().getNodeValue()
    }

    final Element coords= (Element)city.getElementsByTagName("coords").item(0);
    final Element lat= (Element)coords.getElementsByTagName("lat").item(0);
    // вывести lat.getFirstChild().getNodeValue()
    ...
  }
...

Вы можете обратиться к конкретным элементам в документе, например для получения списка городов можно воспользоваться методом getElementsByTagName(), который возвращает массив элементов по имени тега. Можно также использовать метод getFirstChild(), а затем перебирать одноуровневые вершины при помощи метода getNextSibling(). Для обращения к атрибутам следует использовать связку методов getAttributeNode() и getValue(). Кроме того, существуют методы для обработки содержимого секций CDATA, комментариев и других компонентов XML.

Отправка документов XML при помощи GWT

Демонстрационное приложение предоставляет пользователям возможность редактирования данных о численности населения, широте и долготе городов, после чего отправляет эти данные обратно на сервер, который в свою очередь обновляет информацию в базе данных. Ниже будут продемонстрированы два метода создания документа XML: простой, формирующий XML-строку при помощи конкатенации, и более сложный, использующий класс XMLParser для создания необходимой структуры XML.

Примером простого варианта служит метод getCities1(). Для создания XML-строки можно использовать как класс String, так и StringBuffer. В данном случае выбор был сделан в пользу первого, поскольку в этом случае код становится более понятным, хотя второй способ предпочтительнее с точки зрения производительности. В любом случае вам опять придется столкнуться с необходимостью экранирования символов, для чего используется метод 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) {
    // получить значения cityName, countryCode, regionCode, pop, lat и lon из таблицы

    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;
}

Второй алгоритм заключается в использовании метода createDocument() класса XMLParser. Этот вариант очень напоминает работу с функциями SimpleXML в PHP; в частности, вы сначала создаете пустой документ, а затем добавляете в него нужные элементы. Класс XMLParser позволяет добавлять все типы вершин, а также устанавливать значения атрибутов. В итоге следует вызвать стандартный метод toString(), который вернет строковое представление документа XML. После этого останется добавить строку с номером версии и типом кодировки, и строка готова к отправке на сервер. Пример показан в листинге 8 (как и ранее, он был несколько сокращен и модифицирован для ясности).

Листинг 8. Методы класса XMLParser, служащие для создания документов XML, похожи на аналогичные методы в PHP
protected String getCities2() {
  Document xml= XMLParser.createDocument();
  Element cities= xml.createElement("cities");
  xml.appendChild(cities);

  for (all rows in the grid) {
    // получить значения cityName, countryCode, regionCode, pop, lat и lon из таблицы

    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();
}

Чтение документов XML в PHP

Чтение документов XML в приложениях PHP — это хорошо изученная задача, для которой существует множество решений. Однако, по моему мнению, ни одно из них не может сравниться с SimpleXML в части ясности и краткости кода. При использовании SimpleXML вы сначала создаете объект документа, передавая исходный документ XML методу simplexml_load_string(), а затем обходите его содержимое при помощи стандартных операторов PHP. При этом к элементам можно обращаться как к массивам (при помощи метода children()), либо через атрибуты объекта-документа. В свою очередь атрибуты представляются в виде элементов массивов (в качестве примера обратите внимание на чтение кодов стран в листинге 9).

Листинг 9. Пример использования SimpleXML - простейшего способа работы с XML в PHP
$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}')");
}

Существует немало других способов работы с XML в PHP. Ссылки на некоторые из них приведены в разделе "Ресурсы".

Заключение

В этой статье было упомянуто лишь о малой части различных вариантов использования XML в качестве связующего звена между GWT и PHP, однако этого должно хватить для того, чтобы вы смогли начать разрабатывать собственные приложения. Главное – помнить, что работая с GWT, вы отнюдь не ограничены стандартными методами RPC и можете также использовать XML, с легкостью передавая документы между клиентскими и серверными компонентами.


Загрузка

ОписаниеИмяРазмер
Примера кода на Java к статьеjava_source_code.zip4 KБ
Примера кода на PHP к статьеphp_source_code.zip4 KБ

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=XML, Технология Java, Open source
ArticleID=580296
ArticleTitle=XML как связующее звено между GWT и PHP
publish-date=11122010