 | レベル: 中級 Federico Kereki, Systems Engineer, Freelance
2009年 04月 07日 GWT (Google Web Toolkit) アプリケーションでは、昔ながらの Java™ の流儀でサーブレットに接続することもできますが、PHP による Web サービスを使って XML でデータを送受信することもできます。この記事では XML 文書を生成および処理する方法を Java 言語の場合と PHP の場合の両方について説明します。
GWT を利用すると、Java 言語でプログラミングされたサーバー・サイドのサーブレットに容易にアクセスすることができ、データはユーザーにはわからないようにバックグラウンドで、クライアントとサーバーとの間で交換されます。ただし、GWT が通信する相手はサーブレットに限定されるわけではなく、あらゆる種類の 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 とのブリッジとして扱えるようになるためのアドバイスを伝えるためのものです。
テスト・アプリケーション
PHP と GWT とのブリッジとしての XML の使い方を示すために、ここでは、国 (countries)、地域 (regions)、都市 (cities) のデータに基づく簡単なアプリケーションを使用することにします。データベースを作成するリスト 1 のコードを見ると、以下のことがわかります。
- 国 (countries) は一意のコード (例えば、ウルグアイ (Uruguay) の場合は “UY” など) と名前を持っています。
- 国 (countries) は地域 (regions) に分かれており、地域 (regions) は (その国内で一意の) コードで識別され、また名前を持っています。
- 地域 (regions) には都市 (cities) があり、都市 (cities) には名前 (cityName: ASCII のみで表現される名前) と、その国で使われている名前 (cityAccentedName: 英語以外の文字が含まれる場合があります)、人口 (population: 未知の場合は 0)、緯度 (latitude)、そして経度 (longitude) などがあります。都市の名前は、同じ国の別々の地域に現れる可能性があります。
 |
JSON: もう 1 つの有効な選択肢
JSON (JavaScript Object Notation) は元々 JavaScript™ 言語の一部でしたが、やがて JavaScript から独立して JSON そのものが、XML に代わり得る有効な選択肢となりました。JSON は配列やオブジェクトを人間が理解できる単純なテキスト・ベースのフォーマットで表現します。また、同じデータを XML で表現しても JSON で表現してもサイズはほとんど変わりません。Google や Yahoo! など、いくつかの有名なサイトでは XML と共に JSON も提供しています。
JSON の利点は JavaScript で JSON を迅速に処理できることです (例えば JavaScript の 1 つの文を実行することで JSON をオブジェクトに変換することができます)。これは Web 開発者にとって魅力的です。GWT はすべてのクライアント・コードを JavaScript コードにコンパイルするため、GWT は JavaScript のための優れたライブラリーであるとも言え、この記事で紹介する例はすべて XML の代わりに JSON を使ってプログラムすることもできます。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)
);
|
ここでは 1 つのフォームと 2、3 の PHP Web サービスのみを持つ簡単な GWT プロジェクトを作成しています。(完全なソース・コードは「ダウンロード」セクションを参照してください)。このアプリケーションを起動すると、図 1 に示す単純なウィンドウが表示されます。
図 1. 空のフォーム
この GWT フォームでは、都市名の一部を入力して PHP サービスを呼び出し、入力した内容に一致するすべての都市を取得することができます。取得された都市はグリッドの中に表示され、それらの都市の population、latitude、longitude フィールドを編集することができます。編集したデータを別の PHP Web サービスに送信すると、その Web サービスによってデータベースが更新されます。データの転送はすべて XML で行われます。2009年は Charles Darwin の生誕 200 年と彼の著書『The Origin of Species (種の起源)』の発刊 150 周年の両方に当たるので、名前が DARWIN の都市を検索してみましょう。すると図 2 のような結果が得られます。
図 2. 名前に DARWIN が含まれる都市を検索する
追加の構成
参考までに、私は以下を使用しました。
- GWT バージョン 1.5.3
- PHP バージョン 5.2.8
- MySQL® データベース・サーバー・バージョン 5.0.67
- OpenSUSE® バージョン 11.1 の下での Apache バージョン 2.2.10
私はすべてのソフトウェアをそのまま何も特別な設定をせずにインストールしましたが、GWT と PHP との接続をテストするためには GWT に追加の構成ステップが必要です。このステップが必要な理由については「SOP の問題」を参照してください。内部 GWT ブラウザーでの SOP (Same-Origin Policy: 同一生成元ポリシー) チェックを無効にするためには、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 を更新した場合には、この変更を再度行う必要があります。この変更を行わないと、作成したコードはコンパイル・モードでは適切に実行されますがホスト・モードでは失敗することになり、この変更を行うと、作成したコードはコンパイル・モードでは失敗しますがホスト・モードでは実行できることになるので、注意してください。
 |
SOP (Same-Origin Policy) の問題
SOP は基本的に、ある特定の生成元 (つまり URL に含まれるプロトコル、ホスト、ポートの 3 つの組み合わせ) からロードしたページから生成元の異なるデータにアクセスすることを禁止するセキュリティー制約です。(Windows® Internet Explorer® は SOP に関して少し無頓着であり、ポートを変更しても無視しますが、この動作は標準的なものではありません)。例えば GWT Web クライアントが http://www.yoursite.com:80/some/page/at/your/site からロードされたとすると、そのクライアントは SOP の制約から、この URL と異なる URL からデータを取得することができず、他の URL への呼び出しはブロックされます (例えば https://www.yoursite.com はプロトコルが異なり、http://othersite.com はホストが異なり、さらには http://www.yoursite.com:81 もポートが異なるため、これらの URL への呼び出しはブロックされます)。
SOP の考え方は適切です。SOP によって、特定の生成元からの悪意のある JavaScript コードが別の生成元からのデータにアクセスして操作することは不可能になるからです。実際、フィッシャーが最終的に望んでいるのは、SOP を無効にすることです。皆さんが正規のページを見る場合にも、フィッシャーが悪意のあるコードを送り込もうと狙っています。しかし SOP があることで、皆さんがどんな内容を見る場合にも、その内容は想定された生成元から送信されたものであり、(疑わしいと思われる) 他の生成元からのコードはない、と安心することができます。
それとは対照的に、GWT 開発者にとって SOP は迷惑なものです。アプリケーションをホスト・モードで試す場合、アプリケーションはポート 8888 に接続されます。しかしアクセス対象の PHP サービスは必ず標準のポート 80 にあるため、その呼び出しは SOP によって拒否されます。(もちろん、コンパイル・モードでアプリケーションをデプロイした後は、そのアプリケーションは完璧に動作します。そのアプリケーションも SOP に従って標準のポート 80 から実行されるからです。) あらゆる種類のサイトにアクセスしたいわけではなく、同一生成元の別のポートにアクセスしたいだけなのですが、SOP はそれを許可してくれないのです。
|
|
PHP を使って XML を送信する
このアプリケーションは単純なフォームを定義するにすぎません。このフォームを構成する要素にはいくつかのラベルと、1 つのテキスト・ボックス、2 つのコマンド・ボタン、そして結果を表示するグリッドがあります。「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 という 2 つのバージョンがあり、それぞれのバージョンが XML を生成するための異なる方法を示しています。
XML を生成するための最も単純な方法は何と言っても、適切なテキストをシーケンシャルに出力する方法、あるいはストリングを作成してからそれを echo を使って出力する方法です。生成するにあたっては、内容が適切に認識されるようにコンテンツ・タイプを text/xml に設定し、XML のバージョンとデータのエンコーディングを規定するための適切な記述を含んだ行も必ず出力に含めます。また、より小 (<)、より大 (>) アンパサンド (&) などの文字を含まないようにストリングをエスケープする必要があります。そのための最も簡単な方法は PHP 関数 htmlspecialchars() を使う方法です。このコーディングは容易であり、その一部をリスト 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 コードを生成するための第 2 の (そして第 1 の方法ほど長くない) 方法は、XMLWriter を使う方法です。(この XMLWriter の仲間のクラスである XMLReader を使うと XML を処理することができます。) この場合は文字をエスケープする必要はありません (自動的に処理されます)。この方法は先ほどの echo() メソッドによる方法よりも冗長に見えるかもしれませんが、この方法の方がはるかにコードを理解しやすくなります。php://output プロトコルを使っていることに特に注意してください。こうすることで echo コマンドの場合と同じようにテキストが出力されます (リスト 5 を参照)。
リスト 5. XMLWriter を使った単純な方法 (要素を 1 つずつ作成しながら 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 を使うことができます。後ほど SimpleXML を使って XML を読み取りますが、SimpleXML で XML 文書を作成することもできます。また、PEAR フレームワークも調べてみる価値があります。PEAR フレームワークには XML を容易に生成できるクラスがいくつか含まれています (詳細については「参考文献」を参照)。
GWT を使って XML を処理する
GWT には XML の読み取りと作成両方のためのツールとして (com.google.gwt.xml.client パッケージの中に) XMLParser のみが提供されています。parse() メソッドを使って Document を作成し、次にその文書のルート要素を getDocumentElement() を使って取得します。すると XML のウォークスルーを開始することができます。
重要なポイントは、removeWhitespace() メソッドを使って文書から空白を削除する必要があるという点です。ブラウザーのパーサーがタブや改行に対応して空のテキスト・ノードを作成することがあり、そうした空ノードを削除しないとプロセスの途中に予想外の余分な要素が出現し、ロジックがおかしくなる可能性があります (リスト 6 を参照)。もう 1 つのポイントとして、CDATA セクションが予想される場合にはブラウザーが supportsCDATASection() メソッドを受け付けるかどうかを確認する必要があります。このメソッドが受け付けられない場合には CDATA セクションによってテキスト・ノードが生成されます。この詳細については 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()
...
}
...
|
取得したルート要素の配列に対して、getElementsByTagName() メソッドを使用することで、繰り返される要素 (この例では city) を順に取得することができます。もう 1 つの方法は、getFirstChild() メソッドを使用して要素を取得し、取得した要素と同じレベルの残りの要素を、getNextSibling() を使用してウォークスルーします。属性を取得するためには最初に getAttributeNode() メソッドを使用してから getValue() メソッドを使用します。他にもさまざまなメソッドがあり、CDATA セクション、コメント、その他あらゆる XML コンポーネントを処理することができます。
GWT を使って XML を送信する
GWT アプリケーションを利用するとユーザーが population、latitude、longitude フィールドを編集することができ、その編集された cities データをサーバーに送り返すことでデータベースを更新することができます。ここでは 2 つのアルゴリズムを示します。1 つは要素ごとに XML ストリングを作成する単純なアルゴリズムであり、もう 1 つは特定のメソッドを使用して必要な構造を作成する XMLParser ベースのアルゴリズムです。
単純な方のアルゴリズムを getCities1() メソッドとして示します。このメソッドでは、String オブジェクトまたは StringBuffer オブジェクトのいずれかを使って XML を生成します。私はコードが理解しやすくなることから String オブジェクトを使いましたが、パフォーマンスの点からは StringBuffer オブジェクトを使った方が適切でしょう。文字をエスケープする問題を PHP バージョンで説明しましたが、GWT を使用する場合にも同じ問題があります。そこでその問題を Html.htmlspecialchars() メソッドを使って修正します。(リスト 7 を参照。このリストは理解しやすくするために、実際のメソッドを少し変更してあります。)
リスト 7. ストリングを使用して XML の要素を 1 つずつ作成する単純な方法
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 を生成するための、もう 1 つのアルゴリズムが XMLParser クラスの createDocument() メソッドです。PHP での SimpleXML 関数と非常によく似たスタイルで、まず空の文書を作成し、その文書に要素を追加します。この方法で、あらゆる種類のノードを作成することも、属性の値を設定することもできます。最後に、標準的な Java の toString() メソッドによってストリング・オブジェクトの表現を生成します。そのためにはバージョンに関する最初の行とエンコーディングの行のみを追加すればよく、これにより、必要なストリングが得られます。(リスト 8 を参照。このリストは理解しやすくするために、実際のメソッドに対して変更と省略を行っています。)
リスト 8. XMLParser メソッドを使って XML を作成する方法 (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 に匹敵するソリューションはありません。基本的に SimpleXML では XML 文書を simplexml_load_string() メソッドで処理し、その結果を標準的な PHP 演算子を使ってトラバースすることで PHP オブジェクトを作成します。children() メソッドを使うことで、XML 要素の属性は配列の要素になり (例えばリスト 9 での国コードの読み取り方法を見てください)、XML 要素には配列としてアクセスできるようになります。あるいは XML 要素にはオブジェクトの属性として直接アクセスすることもできます。
リスト 9. PHP で XML を処理する方法として最も容易な SimpleXML
$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 を処理する方法には他にも数多くあります。「参考文献」セクションのリンクを参照してください。
まとめ
GWT と PHP とのブリッジとして XML を使用する方法は数多くあり、この記事ではその一部を説明したにすぎませんが、ここで紹介した方法は出発点として十分なはずです。覚えておかなければならない重要なポイントは、GWT では GWT 独自の RPC メソッドしか使えないわけではなく、GWT は XML と共存することができ、XML 文書の生成や利用は容易だということです。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Java source code for this article | java_source_code.zip | 4KB | HTTP |
|---|
| PHP source code for this article | php_source_code.zip | 4KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | 
|  | Federico Kereki はウルグアイ人のシステム・エンジニアであり、システム開発やコンサルティング、大学での教育に 20 年を超える経験があります。現在はおなじみの頭字語、SOA、GWT、Ajax、PHP、そしてもちろん FLOSS を渾然一体に扱いながら業務を行っています。
|
記事の評価
|  |