IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  XML | Web development  >

RSS と Atom を使ってサイトでフィードを取得する

GWT を使って RSS フィードと Atom フィードを取得して処理する

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません

議論する

ダウンロード

原文はこちら

原文はこちら


レベル: 中級

Federico Kereki, Systems Engineer, Freelance

2009年 7月 14日

近年広まっている Web 2.0 サイトでは、さまざまなソースからの情報をマッシュアップすることができます。GWT (Google Web Toolkit) を使用すると、RSS や、もっと新しい Atom 配信フォーマットなどの XML ベースのニュース・フィードを取得して処理することができます。この記事では、SOP (Same-Origin Policy: 同一生成元ポリシー) の制約を克服して任意の適切なフィードを利用できるようにし、フィードとして受信される XML データを処理する方法を学びます。
よく使われる頭字語
  • Ajax: Asynchronous JavaScript + XML
  • API: Application Program Interface
  • RSS: Really Simple Syndication
  • URL: Uniform Resource Locator
  • XML: Extensible Markup Language

近年広まっている Web 2.0 アプリケーションでは、アプリケーションの中で RSS フィードや Atom フィードのデータを取得して使いたい場合があるかもしれません。それを実現する方法としては、GWT と Ajax (Asynchronous JavaScript + XML) を利用することで、RSS フィードや Atom フィードをクライアント・サイドのコードで取得して処理する何通りかの方法があります。ただし、ブラウザーによる SOP などの制約を克服するために、いずれも何らかの対策が必要です。この記事では、フィードの取得と処理の両方に関する問題を解決した、簡単な GWT アプリケーションを検証します。この記事ではフル機能のフィード・リーダーを作成するわけではありませんが、皆さんの Web アプリケーションに RSS とAtom によるフィードを利用できるようになるための指針を提供します。

フィードを取得する

まず、フィードを取得する方法を考えましょう。通常は Ajax を使用します。しかし SOP の問題 (「SOP の問題」を参照) があるため、GWT ページをフィード・ソースに接続することはできません。もちろん、皆さんのサーバー自体がフィード・ソースであれば別ですが、それではあまり面白くありません。

SOP の問題

同一生成元ポリシーはブラウザーのセキュリティーによる制約です。この制約により、ある 1 つの生成元 (1 つの生成元という言葉が意味するのは、最初に要求した URL と同じプロトコル、同じホスト、同じポートを持つ URL のことです) からロードされたページのスクリプトなどが、異なる生成元のデータにアクセスすることはできません (Windows® Internet Explorer® は SOP の制約が緩く、ポートが変更されていても無視しますが、それは標準の動作ではありません)。例えば、ある Web ページが http://www.yourownsite.com:80/some/page からロードされた場合、SOP があるため、そのページのスクリプトなどが以下を始めとする他の URL からデータを取得することはできません。

  • https://www.yourownsite (プロトコルが変更されている)
  • http://othersite (ホストが変更されている)
  • http://www.yourownsite.com:8080 (ポートが変更されている)

セキュリティー上の目的としては、SOP は確かにとても理に適っています。SOP があることによって、1 つの生成元から得られたページのスクリプトなどが、異なる生成元のデータにアクセスして、そのデータを操作、表示することはできなくなるからです。SOP がなくなれば、フィッシング詐欺を行う者達にとっては理想的な環境になります。信頼できる Web サイトの有効なページを見ている場合でも、その際のアクションや送受信されるデータを第三者がモニターできてしまうからです。SOP があるおかげで、どのようなデータを受信する場合も、そのデータが確かに最初に要求した Web サイトから送信されたものであることが保証されます。他の (おそらく怪しい) 生成元からのコードは、どのようなコードであっても受信することはできないのです。

この制約があることを考慮すると、フィードを取得する方法として考えられる選択肢は以下の 2 つです。

  • Ajax を使って皆さんのサーバーのプロキシーに接続します。そのプロキシーがフィードを取得し (コードがブラウザーで実行されているのでなければ SOP の制約はありません)、そのフィードをページに返送する方法。
  • JavaScript に関連する (Google の API を利用した) 興味深い手法を利用して SOP をバイパスする方法。

この記事では最初にプロキシーを使用する手法を学び、次に Google AJAX Feed API を使用する方法を学びながら、Java™ と JavaScript のコーディングを混在させる方法を学びます。

Web ページの設計

まず、基本となるページを設計します。このページには、必要なフィードの URL を入力するためのテキスト・ボックス、フィールドの取得方法を選択するためのリスト・ボックス (実際には、こうした選択肢をユーザーに与えることはありません)、そしてデータの取得を実行するためのコマンド・ボタンがあります。図 1 はまだフィードを取得していない状態の基本となるウィンドウを示しており、デフォルトの URL が表示されています。ところで、この図で使用しているブラウザーがどのブラウザーなのかわからない人のために言うと、これは GWT 独自の、ホスト・モードの Mozilla ベースのブラウザーです。もちろん、このアプリケーションをコンパイルしてデプロイした後は、任意のブラウザーを使うことができます。


図 1. まだフィードを取得していない状態のページ

フィードを取得すると、何も特別な処理はせず、ニュースのメイン・タイトル、簡単な説明、そしてリンクを単純に表示します。このページでは取得されるフィードの数に制限はなく、新しいデータが表示される前には、それまで表示されていたデータは画面から消去されます。図 2 は実際にフィードの取得を実行した例を示しています。


図 2. フィードを取得した結果

まとめとして、このプログラムの全体的な構造を見てください (リスト 1)。わかりやすくするために、ここに示したコードは各メソッドの簡単な説明を付けてかなり省略してあります。後ほど各メソッドのコードを検証します。オリジナルの完全なソース・コードは「ダウンロード」セクションを参照してください。


リスト 1. 主なコードの全体的な構造

package com.fkereki.rssread.client;

//... "import" lines ...

public class Rssreader implements EntryPoint {
  // ... variable definitions...

  public void onModuleLoad() {
    // set up the form and its fields

    // call getFeedViaProxy(...) or getFeedViaGoogle(...)
    // depending on the listbox value
  }

  void getFeedViaProxy(final String feedUrl) {
    // connect to the remote server via RPC
    // when data arrives, call processAndShowFeed(...)
  }

  native void getFeedViaGoogle(final String feedUrl) /*-{
    // call Google Feed API (using native JavaScript, not Java)
    // when data arrives, call processAndShowFeed(...)
  }-*/;

  void processAndShowFeed(final String xmlDocument) {
    // clear results from a previous run, if any

    // decide whether it's RSS or Atom, and call
    // processRssFeed() or processAtomFeed() as required
  }

  void processRssFeed(final Element root) {
    // navigate a RSS feed, extraction titles, descriptions,
    // and links, and using showFeedItem(...) to show them
  }

  void processAtomFeed(final Element root) {
    // navigate an Atom feed, extraction titles, descriptions,
    // and links (by using getValueIfPresent(...) and
    // getLinkIfPresent(...), and showFeedItem(...) to show them
  }

  private String getValueIfPresent(final Element el, final String tn) {
    // get an XML node, and return the value that corresponds to a certain tag
  }

  private String getLinkIfPresent(final Element el) {
    // given a XML "link" node, return the corresponding address
  }

  private void showFeedItem(final String title, final String description,
      final String link) {

    // add some lines to the screen, with the data for the latest news
  }
}

プロキシーを使う

GWT では、RPC (Remote Procedure Call) を使うことでサーバー・サイドのサーブレットに容易にアクセスすることができます。しかしそのためには、クライアント・サイド・コード用のいくつかのインターフェースと、サーバー・サイド・コードとしての実際のサーブレットのプログラムを作成する必要があります。最初にサーバー・サイドのコードを考えましょう。この場合に必要なサービスは、フィードの URL が指定されたらそのサイトに接続し、サイトのコンテンツをダウンロードして、そのコンテンツを呼び出し側に返送することです。(対応するシェル・コマンドとして wget コマンドや curl コマンドを考えてみてください)。そのための方法はたくさんありますが、簡単に実現する方法をリスト 2 に示します。ここではリモート・プロキシーを RemoteProxy と呼ぶことに決めたので、サーバー・サイドのクラスを RemoteProxyImpl と呼ぶようにします (Impl は「Implementation (実装)」を表します)。


リスト 2. GWT のサーブレット・プロキシー

package com.fkereki.rssread.server;

//... "import" lines...

public class RemoteProxyImpl
    extends RemoteServiceServlet implements RemoteProxy {

  //... variable definitions ...

  public String getFeed(final String feedUrl) {
    String result= "";

    try {
      final BufferedReader in= new BufferedReader(new InputStreamReader(
          new URL(feedUrl).openStream()));

      String inputLine;
      while ((inputLine= in.readLine()) != null) {
        result+= inputLine;
      }

      in.close();
      return result;

    } catch (final Exception e) {
      return "";
    }
  }
}

サーバー・サイドのコードが作成できたので、今度はクライアント・サイドに必要なコードの作成に移りましょう。GWT の命名規則に従って、リスト 3 の 2 つのインターフェース (RemoteProxyRemoteProxyAsync) を実装する必要があります。この 2 つのインターフェースを使って Ajax スタイルで非同期コールバックを行うと、リモート・プロキシーが値を返します。


リスト 3. RPC による呼び出しのために 2 つのインターフェースが必要なクライアント・サイド

@RemoteServiceRelativePath("remoteProxy")
public interface RemoteProxy extends RemoteService {
  public String getFeed(String feedUrl);
}

//

public interface RemoteProxyAsync {
  void getFeed(java.lang.String feedUrl,
      com.google.gwt.user.client.rpc.AsyncCallback<String> arg2);
}

この 3 つのコードを組み合わせると、リスト 4 のように容易にフィードを取得することができます。


リスト 4. 上記のインターフェースを使って Ajax スタイルのコールバックを行う

  void getFeedViaProxy(final String feedUrl) {
    final RemoteProxyAsync rp= (RemoteProxyAsync)GWT
        .create(RemoteProxy.class);

    rp.getFeed(feedUrl, new AsyncCallback<String>() {
      public void onFailure(final Throwable caught) {
        Window.alert("failure?!");
      }

      public void onSuccess(final String result) {
        processAndShowFeed(result);
      }
    });
  }

フィードの URL が指定されたら、RPC の詳細部分を処理する適当なクラスを GWT のメカニズムを使って作成します (詳細部分というのは、データのシリアライズ、コールバックの設定、サーバー・サイドのサーブレットの実際の呼び出し、受信された応答のデシリアライズ、などです)。また、非同期コールバックのプログラムも作成し、データが返送された場合に何をするのかを以下のように指定します。

  • エラーが発生すると、onFailure メソッドが呼び出されます。
  • RPC による呼び出しに成功すると onSuccess メソッドが呼び出され、この場合は受信された XML を処理してフィードを画面上に表示します。

Google AJAX Feed API を使う

通常、ブラウザーには SOP の制約があるため、別のサイトからのデータをコードで取得することはできませんが、例外があります。その例外とは、<script ... /> タグを使うと JavaScript コードをダウンロードして実行することができるのです。もしダウンロードしたコードがデータを含んでおり、しかもそのデータを適切に利用するためにユーザーが用意した関数を呼び出すとすると、SOP をバイパスできたことになります。Google AJAX Feed API の背後にある考え方は以下のとおりです。

  1. <script ... /> タグを使って、プロキシーとして動作する Google のサイトを呼び出します。
  2. リモート・サイトがフィード・データを取得し、そのデータを JavaScript コードの形式で返します。
  3. ダウンロードされた JavaScript コードが、ユーザーが用意した関数を呼び出し、受信される XML をその関数が処理します。

Google AJAX Feed API は JavaScript コードで作成されているため、GWT の JSNI (JavaScript Native Interface) を使う必要があります。まず、リスト 5 のように jsapi スクリプトを含めます。Google のサイトから直接 jsapi スクリプトを含めることも (このことから、この状況には SOP が適用されないことが確認できます)、皆さんのサーバーから jsapi スクリプトをダウンロードして含めることもできます。このスクリプトによって、後ほど使用するグローバルな google オブジェクトが作成されます。リスト 5 の “mandatory initialization” (強制的な初期化) の行からわかるように、このグローバルな google オブジェクトは、使用する前に、初期化する必要があります。


リスト 5. Google AJAX Feed API 関連のコーディングを含む Web ページ

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <!-- ... -->
  </head>

  <script type="text/javascript" src="http://www.google.com/jsapi"></script>

  <script type="text/javascript">
    google.load("feeds", "1");  // mandatory initialization
  </script>

  <body>
    <!-- ... -->
  </body>

</html>

リスト 6 は必要な JSNI コードを示しています。ここではすべての詳細は説明しませんが、以下のことに注意してください。

  • native アクセス修飾子によって、getFeedViaGoogle メソッドが Java コードではなく JavaScript コードでプログラミングされていることを強調しています。
  • /*-{}-*/ はメソッドの実装の区切りです。
  • グローバル変数 $wndgoogle 変数にアクセスします (GWT はコンパイルされたアプリケーション・コードを子の iframe にロードします。そのため、メイン・ウィンドウの中のどれに直接アクセスしようとしても失敗します)。
  • 受信された XML 文書に対して serializeToString パラメーターがあるのは、processAndShowFeed メソッドが String パラメーターを要求するためです。
  • 少し複雑な構文が必要な理由は、Java メソッドを呼び出すためです。
  • (this を直接使う代わりに) myself 変数を使っている理由は、こうしないとクロージャーが機能しないからです (「参考文献」を参照)。

リスト 6. Google AJAX Feed API と JSNI を使ってフィードを取得する

native void getFeedViaGoogle(final String feedUrl) /*-{
  var myself= this;
  var feed= new $wnd.google.feeds.Feed(feedUrl);
  feed.setResultFormat($wnd.google.feeds.Feed.XML_FORMAT);
  feed.load(function(xmlResult) {
    myself.@com.fkereki.rssread.client.Rssreader::processAndShowFeed(Ljava/lang/String;)
      ((new XMLSerializer()).serializeToString(xmlResult.xmlDocument));
  });
}-*/;

JSNI の詳細を別にすると、このコードは単純です。取得対象の URL を指定する feed オブジェクトを作成し、取得するフォーマットを XML に設定します。そして load 関数を呼び出してコールバック関数を引数として渡し、このコールバック関数によってフィードを (ストリングに変換して) processAndShowFeed メソッドに渡しています。




上に戻る


フィードを使用する

RSS フィードも Atom フィードも XML 文書であるため、GWT の XMLParser をそのまま使用することができます。空ではない XML ストリングがある場合、この XMLParser を使用して XML ストリングを構文解析し、ルート要素を取得します。そのフィードが RSS なのか Atom なのかを容易に判断するためには、ルート・ノードの名前を調べます。RSS であれば名前が rss であり、Atom であれば名前が feed です。一致するものがない場合には、エラーを生成します。最後に、適切なメソッドを呼び出し、その XML 文書をウォークスルーして、表示用の部分を取得します (リスト 7)。


リスト 7. フィードを表示する

void processAndShowFeed(final String xmlDocument) {
  if (xmlDocument.isEmpty()) {
    // warn about the problem; most likely, a wrong URL

  } else {
    final Document xmlDoc= XMLParser.parse(xmlDocument);
    XMLParser.removeWhitespace(xmlDoc);
    final Element root= xmlDoc.getDocumentElement();

    // clear out the previous feed results, if any,
    // to make space for the feed that is to be loaded

    if (root.getNodeName().equals("rss")) {
      processRssFeed(root);

    } else if (root.getNodeName().equals("feed")) {
      processAtomFeed(root);

    } else {
      // warn about the unknown feed format
    }
  }
}

リスト 8 は (少し省略した) RSS フィードを示しています。


リスト 8. (少し省略した) RSS ニュース・フィード

<?xml version="1.0" encoding="ISO-8859-1"?>
<rss xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
  <channel>
    <title>CNN.com - Technology</title>

    <link>http://edition.cnn.com/TECH/?eref=edition_technology</link>
    <description>CNN.com delivers up-to-the-minute news ...</description>
    <language>en-us</language>
    <copyright>© 2009 Cable News Network LP, LLLP.</copyright>

    <pubDate>Thu, 16 Apr 2009 20:56:04 EDT</pubDate>
    <ttl>10</ttl>

    ... several more channel properties

    <item>
      <title>YouTube orchestra wows Carnegie Hall</title>

      <link>http://rss.cnn.com/~r/rss/edition_technology/~3/XxF062aMfCI/index.html</link>
      <description>The YouTube and Carnegie Hall generations ...</description>
      <pubDate>Thu, 16 Apr 2009 15:28:47 EDT</pubDate>
    </item>

    <item>
       ... another news item...
    </item>

    <item>
       ... yet another item...
    </item>
  </channel>

</rss>

RSS の XML フィードを処理するためのメソッドと Atom の XML フィードを処理するためのメソッドは、ほとんど同じです。基本的に、すべてのニュース・ノード (RSS の場合は item、Atom の場合は entry) を取得するには getElementsByTagName を使います。注意する点として、RSS の場合には 1 つレベルを下げ、中間の channel ノードをスキップする必要があります (リスト 9)。次に、ニュース項目を 1 つずつ調べ、titledescription (Atom の場合は summary)、そして link を抽出して表示します。getValueIfPresent メソッドは title と description を処理します。getLinkIfPresent メソッドは RSS と Atom との間の違いを処理します (RSS の場合はノードの値は実際のリンクですが、Atom の場合には href 属性を使う必要があります)。


リスト 9. RSS フィードを処理するためのコードの一部

void processRssFeed(final Element root) {
  final NodeList items=
    ((Element)root.getElementsByTagName("channel").item(0)).
      getElementsByTagName("item");

  for (int i= 0; i < items.getLength(); i++) {
    final Element item= (Element)items.item(i);
    final String rssDescription= getValueIfPresent(item, "description");
    final String rssTitle= getValueIfPresent(item, "title");
    final String rssLink= getLinkIfPresent(item);

    // display rssTitle, rssDescription and rssLink onscreen
  }
}

private String getValueIfPresent(final Element el, final String tn) {
  final NodeList nl= el.getElementsByTagName(tn);
  if (nl.getLength() == 0) {
    return "";
  } else {
    return nl.item(0).getFirstChild().getNodeValue();
  }
}

private String getLinkIfPresent(final Element el) {
  final NodeList nl= el.getElementsByTagName("link");
  if (nl.getLength() == 0) {
    return "";
  } else {
    if (nl.item(0).hasChildNodes()) {
      return nl.item(0).getFirstChild().getNodeValue();
    } else {
      return ((Element)nl.item(0)).getAttribute("href");
    }
  }
}

ここで、白状しておかなければならないことと、注意しなければならないことが 1 つあります。この記事に付属の Atom フィードを処理するコードは、あまり深く考えず、summary が表示可能なテキストだと想定しています。より安全なコードにするためには、type 属性を調べ、表示可能であることを確認する必要があります。場合によると summary は表示不可能なものかもしれません (例えば XML、base64 でエンコードされたバイナリー・コンテンツ、あるいは他のコンテンツへのポインターなど)。しかしその可能性は低いため、ここではコードを単純にしました。従って、本当に一般的な Atom リーダーを作成したい場合には、以上の点に注意してください。




上に戻る


まとめ

この記事では、GWT の Web ページから RSS フィードと Atom フィードを直接取得して処理するための 2 つの異なる方法を説明しました。ここで紹介したコードは、XML フィードそのものを直接扱うことで、基本となるフィード処理を行います。このコードを拡張し、より高度な処理をすることも、それほど複雑ではないはずです。しかもどんなフィードでも皆さんの Web ページで使用できるのです。ぜひ試してみてください。





上に戻る


ダウンロード

内容ファイル名サイズダウンロード形式
Sample GWT code for this articlerssreader.tar.gz4305KBHTTP
ダウンロード形式について


参考文献

学ぶために

製品や技術を入手するために
  • 最新バージョンの GWT を入手し、この記事のコードを試してみてください。

  • Eclipse Ganymede の最新版を試してみてください。著者はこれを開発環境として使っています。

  • Google Plugin for Eclipse を使うと迅速に作業を開始することができます。

  • サーバー・サイドの Java コードで RSS フィードと Atom フィードを処理したい場合には、Project ROME を試してみてください。

  • Python ユーザーであれば、RSS フィードと Atom フィードを処理するためのパーサーとして Universal Feed Parser が気に入るかもしれません。

  • IBM 製品の試用版をダウンロードするか、あるいはオンラインで IBM SOA Sandbox を試し、DB2®、Lotus®、Rational®、Tivoli®、WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品を試してみてください。


議論するために


著者について

Photo of Federico Kereki

Federico Kereki はウルグアイ人のシステム・エンジニアであり、システム開発やコンサルティング、大学での教育に 20 年を超える経験があります。現在はおなじみの頭字語、SOA、GWT、Ajax、PHP、そしてもちろん FLOSS を渾然一体に扱いながら業務を行っています。




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



 


 


不充分・不完全である大変素晴らしい
 


この記事を共有する

del.icio.us del.icio.us newsing newsing FC2ブックマーク FC2ブックマーク
Choix! Choix! ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
MM/memo MM/memo CZブックマーク CZブックマーク livedoorクリップ livedoorクリップ
はてなブックマーク はてなブックマーク Buzzurl(バザール) Buzzurl(バザール)




上に戻る


    日本IBMについて プライバシー お問い合わせ