本文へジャンプ

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。プロフィールで選択した情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

  • 閉じる [x]

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


送信されたすべての情報は安全です。

  • 閉じる [x]

Google Web Toolkit、Apache Derby、Eclipse を使用して Ajax アプリケーションをビルドする 第 3 回: 通信

リモート・プロシージャー・コール (RPC) と Google Web Toolkit

Noel Rappin, Senior Software Engineer, Motorola, Inc.
ジョージア工科大学 Graphics, Visualization, and Usability Center 出身の博士、Noel Rappin は、Motorola 社のシニア・ソフトウェア・エンジニアです。「wxPython in Action」および「Jython Essentials」の共著者でもあります。

概要: この連載ではこれまで 2 回の記事で、GWT (Google Web Toolkit) と Apache Derby リレーショナル・データベースによる単純なWeb アプリケーションの作成方法を紹介してきました。第 1 回では、GWT を使用して Slicr というピザ宅配システムのフロントエンドをビルドする方法に焦点を当てました。 第2 回では、Derby を使って実際にリレーショナル・データベースを作成し、データベースの行を Java™ オブジェクトに変換するメカニズムの基本を説明しました。話がいよいよ面白くなってきたところで、第3 回となるこの記事では、クライアントとサーバーを互いに対話させる方法を説明します。GWT 内のリモート・プロシージャー・コール (RPC)フレームワークを使えば、Java メソッド呼び出しを作成するのと同じくらい簡単にサーバーからデータを取得できるようになります。

日付:  2007年 2月 13日
レベル:  中級 この記事の原文:  英語
アクティビティー: 2731 ビュー
お気軽にご意見・ご感想をお寄せください: 


Ajax の A

Ajax (Asynchronous JavaScript + XML) 対応 Web アプリケーションと従来の Web アプリケーションとの重要な違いは、Ajaxの A、つまり非同期であることです。Ajax アプリケーションでは、ブラウザーがページ全体を完全に更新することなく、ページの特定部分だけを更新することができます。この単純な芸当によってWeb ページの振る舞いはデスクトップ・アプリケーションにごく近いものとなり、はるかに対話性の優れたユーザー・エクスペリエンスが実現します。

開発者の視点から見ると、この非同期の振る舞いには 2 つの重要な構成部分があります。

  • XMLHttpRequestオブジェクトはブラウザーによって定義される JavaScript オブジェクトの 1 つです。このオブジェクトにより、Web ページがバックグラウンドのスレッドでHTTP 要求を送信し、応答を受信することが可能になります。通常のページ要求とは異なり、呼び出しによってユーザー・エクスペリエンスが中断されることも、ブラウザーが応答を待機している間に一時停止することもありません。
  • ある種のコールバックが、応答の完了後に実行されます。このコールバックは通常、新規データに応じてページの要素を操作するために JavaScript文書オブジェクト・モデル (DOM) を使用します。これにより、画面に素晴らしい効果が生まれ、ユーザーを満足させるというわけです。
Ajax Resource Centerにアクセスしてください。ここには記事、チュートリアル、ディスカッション・フォーラム、ブログ、ウィキ、イベント、そしてニュースなど、Ajax プログラミング・モデルに関する情報が豊富に用意されており、ワンストップ・ショップになっています。新しい情報もここに記載されます。

基本的な流れとしては、サーバーを呼び出し、応答が返り、そして応答に基づいてページに対するアクションが実行されます。繰り返しますが、念頭に置いておかなければならない重要な点は、このすべてが裏で行われるということです。ブラウザーでの一般的なページ変更に伴う待機時間や更新時間に煩わされることはありません。

XMLHttpRequest/DOM メソッド全体に、1 つだけ問題があります。一種の頭痛の種となっているこの問題は、それぞれのブラウザーが関連 JavaScript オブジェクトを実装する方法はまちまちだということです。これはつまり、サーバーにデータを送るのに手こずる場合があるということで、したがって応答を有用なデータに変換するのも厄介になることがあります。そのため、Ajaxの名に値するすべてのフレームワークには、RPC の作成、呼び出し、管理を簡易化するある種のラッパーがあります。GWT も例外ではありません。

GWT は RPC の管理に、多重インターフェース・インフラストラクチャーを用います。これは EJB (Enterprise JavaBeans)技術のようなものを連想させますが、ありがたいことに、それよりはもっと単純です。取り決めとしては、まずユーザーが、システムが行うリモート・コールのリストを定義します。GWTは、サーバーに送るためにデータを変換し、サーバーを呼び出し、そしてサーバーから返ってきたデータをクライアント・データに変換するという一連の動作を裏で実行します。リモート・コールから戻った後の実行内容は、ユーザーが定義します。これは一般的なJava メソッド呼び出しを行うようには簡単にはいきませんが、難しいというわけでもありません。

Slicr アプリケーションに対して行う変更は、トッピングの初期リストをクライアント・コードにハードコーディングする代わりに、サーバーから取得するようにすることです。この明らかに単純化した例で、RPCによる呼び出しを行うために必要なステップをすべて見ていきます。この例を簡単に実行できるように、ここではホスト・モードを使用します。ホスト・モードでは、GWTが自動的にリモート・コールをシミュレートしてくれます。Eclipse や IntelliJ IDEA などの統合開発環境 (IDE) を使用している場合は、ホスト・モードでの作業がサポートされます。

注: 連載最終回では、通常のサーブレット環境でサーバー・サイドを実行する方法を紹介します。

サービスの定義

GWT プロシージャー・コールでの大半の操作は、2 つのクラスで行われます。まず、サーバー・サイドで定義するのは RemoteServiceServlet のサブクラスです。このクラスでは、サーバー・データを操作してクライアントに値を返します (あるいは例外をスローしますが、とりあえずは楽観的な姿勢でいましょう)。一方、クライアント・サイドで定義するのは、AsyncCallback インターフェースを実装するクラスです。このクラスでは、クライアント・ページがサーバーから渡されたデータ (または例外) で実行する内容を定義します。この2 つのクラスに加え、GWT がクライアント・サイドのクラスとサーバー・サイドのクラスを結合できるようにするためのグルー・コードを多少作成する必要があります。多少、というより実は大量のグルー・コードです。グルー・コードは2 種類のインターフェース、クライアント・サイドのコード、そして 1 つあるいは 2 つの設定値からなります。でも心配は無用です。コードの大半は定形文面なので、順を追っていけば問題ありません。

まずはサーバー・サイドから取り掛かります。ここでの目標は、前回の記事の終わりでデータベースに配置したすべてのトッピングのリストを作成することだけです。サーバーは前回の記事に記載した単純な ObjectFactory (リスト 1 を参照) を使用するので簡単です。


リスト 1. トッピング・サービスの実装
                
public class ToppingServiceImpl extends RemoteServiceServlet 
                implements ToppingService {

        public static final String DRIVER = "org.apache.derby.jdbc.EmbeddedDriver";

        public static final String PROTOCOL = "jdbc:derby:slicr;";

        public List getAllToppings() {
                try {
                        Class.forName(DRIVER).newInstance();
                        Connection con = DriverManager.getConnection(PROTOCOL);
                        Statement s = con.createStatement();
                        ResultSet rs = s.executeQuery(
                                "SELECT * FROM toppings order by name");
                        return ObjectFactory.convertToObjects(rs, Topping.class);
                } catch (Exception e) {
                        e.printStackTrace();
                        return new ArrayList();
                } finally {
                        try {
                                DriverManager.getConnection("jdbc:derby:;shutdown=true");
                        } catch (SQLException ignore) {}
                }
        }

}


このコードで第一に注目する点は、手の込んだところはまったくないということです。GWT リモート・サービスに必要なのは、RemoteServiceServlet を拡張して、2 つくらいのパラグラフを作成するためのインターフェースを実装するだけです。

注: 大抵の GWT 資料では、インターフェースを先に作成させているようですが、それでも構いません。ここでは、先に具体的なコードに取り掛かったほうがわかりやすいと思っただけです。

他に注目する点として、このコードの機能は前回の記事の ToppingTestr の例とほとんど同じで、GWT 内で使用するためにパッケージしなおされています。ここでは Derby データベースを呼び出してオブジェクト・ファクトリーを使ってトッピング・オブジェクトを作成し、作成したトッピング・オブジェクトを返しています。

グルー・コード

新しいサービスをクライアント・サイドのアプリケーションで使用できるようにするには、関連する 2 つのインターフェースを定義する必要があります。最初に定義するインターフェースは、説明はしていませんでしたがリスト 1 で使用した ToppingService です。このインターフェースは、リスト 2 に示すように実に単純なものです。


リスト 2. 最初の関連インターフェース、ToppingService の定義
                
public interface ToppingService extends RemoteService {
        public List getAllToppings();
}

上記では、実際の具体的なクラスのメソッドに使用したシグニチャーと同じシグニチャーを取っているだけです。主な制約は、インターフェースが com.google.gwt.user.client.rpc.RemoteService; を拡張しなければならないということです。また、パラメーターと戻り値は GWT で直列化可能な型でなければなりません。GWT で直列化可能な型のリストは第 2 回で記載しています。ただし、サービス・インターフェースのバージョンは 1 つでは足りません。リスト 3 に示す非同期バージョンのインターフェースも定義する必要があります。


リスト 3. 非同期バージョンのインターフェースの定義
                
public interface ToppingServiceAsync {
        public void getAllToppings(AsyncCallback callback);
}


非同期バージョンのサービス・インターフェースは、上記で説明した基本バージョンからの派生です。2 つのバージョンは同じパッケージにあり、そのパッケージはGWT クライアント・コードに見えなければなりません (私は com.ibm.examples.client を使用しました)。非同期バージョンでのクラス名は元のインターフェース名の末尾に Async というストリングを追加した形になります。非同期バージョンには、元のインターフェースに含まれるそれぞれのメソッドに対応するメソッドが必要です。これらのメソッドの戻りの型はvoid になっており、AsyncCallback 型のパラメーターが追加されています。これは、クライアント・サイドのコードは、サーバーの応答に対処するために AsyncCallback を使用するからです。

単一のサービス・インターフェースで許容可能なメソッド数には制限がありません。すべてのメソッドが非同期バージョンに兄弟を持ち、同じリモート・サービス・クラスに実装されている限り、好きなだけメソッドを設定することができます。どのようにサービスを構成するかはほとんど感覚次第ですが、サーバー・サイドの共通データをサービス・メソッドが共有できるようにすると、実用的価値があると思います。

もう 1 つ必要なこととして、以下の行を Slicr.gwt.xml ファイルに追加してください。この行を追加すると、サーバー・コードが登録されます。

<servlet path="/toppings" class="com.ibm.examples.server.ToppingServiceImpl"/>

上記の行は、具体的なリモート・サービス・クラスの完全修飾クラス名を、基本的にこのサービスの URL であるパス名に組み合わせたものです。覚えていられて、この後も一貫している限り、どんな名前でも使えます。形の上では、アプリケーションをホスト・モードで実行する場合に.xml ファイルに組み込む必要があるのはこの行だけです。次回の Web デプロイメントで説明するように、web.xml ファイルにも同じような行が必要です。

単一の呼び出しで一貫していなければならない項目は、インターフェース、非同期インターフェース、実際の実装、そしてモジュール・ファイルと数多くあります。その1 つが欠けていたり整合していない場合、実際にサービスを呼び出そうとするとエラーになります。この記事を書いている時点で、このすべての項目を一貫したものにするためのサポート機能がエディターにあるIDE は少なくとも 1 つ (IntelliJ IDEA) あります。Eclipse を使用している場合は、Googlipse プラグインも同様のサポートをしています。


クライアント・サイドを呼び出す

サーバー・サイドの作業が終わったら、次はクライアントがプロシージャー・コールを行う番です。基本的な概念としては、まず、どのリモート・サービスを呼び出しているのかをリモート・GWTシステムに慎重に伝え、次に AsyncCallback オブジェクトをネットワークへ送信します。すると最終的に GWT からこのオブジェクトが送り返され、結果に応じた動作が可能になります。リスト2 に、セットアップと呼び出しのためのコードを示します。このメソッドは第 1 回で説明したSlicr クラスです。具体的には、このメソッドの呼び出しはトッピング・パネルを追加した Slicr.onModuleLoad() の行に置き換わります。


リスト 4. セットアップと呼び出し
                
public void callForToppings() {
        ToppingServiceAsync toppingService = 
                (ToppingServiceAsync) GWT.create(ToppingService.class);
        ServiceDefTarget target = (ServiceDefTarget) toppingService;
        String relativeUrl = GWT.getModuleBaseURL() + "toppings";
        target.setServiceEntryPoint(relativeUrl);
        toppingService.getAllToppings(new ToppingCallback());
}

上記のそれぞれの行が、GWT の呼び出しで重要なステップとなります。チェックリストは以下のとおりです。

  1. 非同期インターフェースのインスタンスを作成します。もちろん通常はインターフェースのインスタンスを作成することはできないので、ここで GWT.create() の呼び出しが登場します。この GWT クラスは複数のユーティリティーに対応でき、この例では指定されたインターフェースを実装するプロキシー・オブジェクトを作成できるようにしています。デプロイメントでは、このメソッドの引数は変数ではなく、リテラルでなければなりません。一方ホスト・モードでは、変数でないと機能しませんので注意が必要です。
  2. GWT.create() が戻すプロキシー・オブジェクトはもう 1 つのインターフェース、ServiceDefTarget も実装します。1 行か 2 行のうちに、このインターフェースのメソッドが必要になります。
  3. メッセージの送信先 URL を計算します。URL には以下の 2 つのコンポーネントがあります。
    • システムの基本 URL。GWT.getModuleBaseURL()を呼び出して取得します。
    • サーブレットを .xml ファイルに追加したときにパスとして使ったストリング
  4. Armed with the full URL, you can inform GWT that this particular URL isthe place to go for this particular service by calling the setServiceEntryPoint() method. The interface also contains the related getter method. At thispoint, the service is fully initialized for use in this client page.
  5. And finally -- drum roll, please -- you can make the actual call to theservice, just as the service defines it (except for that ToppingCallback object, which is the subject of the next section).

完全な URL の用意ができると、setServiceEntryPoint() メソッドを呼び出して、GWT にこの特定の URL がこの特定のサービスの場所であることを通知できます。インターフェースには関連するゲッター・メソッドも含まれています。この時点で、サービスはこのクライアント・ページ用に完全に初期化されます。

そしていよいよ (ドラム・ロールをお願いします)、このサービスが定義しているとおりの実際のサービス呼び出しを実行できます (ただし、ToppingCallback オブジェクトの場合は例外です。このオブジェクトについては次のセクションで説明します)。

クライアント・ページでは、サービス (サンプルでは先頭の 4 行) を 1 回初期化するだけで済みます。サービスが作成されてエントリー・ポイントに関連付けられると、同じサービス・オブジェクトを再利用してサーバーの呼び出しを何度も実行できるからです。また、ヘルパー・メソッドを作成して、サーバー・オブジェクトの定形文面の初期化を簡単にすることもできます。


非同期の醍醐味

前にそれとなく触れたように、GWT RPC と通常のメソッド呼び出しの大きな違いは、リモート・コールはいつ完了するか、あるいは完了するかどうかがわからないことです。Webユーザーは大抵、隅にある選択ボックスの更新中にページ全体がフリーズするのを快く思わないので、GWT プログラムはリモート・コールを行ってその軽やかな動作を続行します。最終的にサーバーが何とか応答した時点ではじめて、あなたが計画した素晴らしいことをGWT コードが実行します。

このような環境では、バックグラウンド・スレッドを追跡するのが困難になることがよくあります。ただしサンプル・アプリケーションでは、GWT は上記で説明したメカニズムと、この記事ですでに取り上げたAsyncCallback インターフェースを使ってほとんどの作業を行います。GWT はこのようにして、複数のスレッドを処理する際のオーバーヘッドを効率的に単純化します。

AsyncCallback インターフェースは、OnSuccess(Object obj)OnFailure(Throwable t) という 2 つのメソッドを定義しているので、この両方を実装するクラスを定義しなければなりません。リスト 4 に示したように、リモート・コールを実行するときに、クラスのインスタンスを作成して非同期サービス・メソッドに渡します。最終的にサーバー・サイドのリソースが完成すると、コードに含まれる2 つのメソッドのうちのどちらかが呼び出されます。成功メソッドの引数はインターフェースと実装での呼び出しの戻り値で、この例ではトッピングのリストとなります。この値を期待する型にキャストしてから、この新しいデータを使って有益なことを行います。近いうちにGWT が Java 1.5 の Generics をサポートするようになれば、キャスト作業の必要性は減ることでしょう。一方、サーバー・サイドのコードが失敗すると、スローされた例外を引数として失敗メソッドが呼び出されます。この場合も、例外に応答して実行する内容は自由に決められます。

リスト5 のコードに、リスト 4 で参照した ToppingCallback クラスを示します。前に言ったかもしれませんが、AsyncCallback クラスは匿名内部クラスとして定義されていることがよくあります。このように定義するのは、なるべく避けることをお勧めします。クラスに名前があり、まったく異なるコード・ブロックの真ん中に配置されていないほうが、コードが読みやすくなり、テストもしやすくなります。


リスト 5. ToppingCallback クラス
                
public class ToppingCallback implements AsyncCallback {

        public void onFailure(Throwable caught) {
            GWT.log("Error ", caught);
                caught.printStackTrace();
        }

        public void onSuccess(Object result) {
                List allToppings = (List) result;
                VerticalPanel toppings = new VerticalPanel();
                toppings.add(new HTML("<h2>Toppings</h2>"));
                Grid topGrid = new Grid(allToppings.size() + 1, 3);
                topGrid.setText(0, 0, "Topping");
                topGrid.setText(0, 1, "Left");
                topGrid.setText(0, 2, "Right");
                for (int i = 0; i < allToppings.size(); i++) {
                        Topping t = (Topping) allToppings.get(i);
                        Button button = new Button(t.getName() );
                        CheckBox leftCheckBox = new CheckBox();
                        CheckBox rightCheckBox = new CheckBox();
                        clearables.add(leftCheckBox);
                        clearables.add(rightCheckBox);
                        button.addClickListener(new ToppingButtonListener(leftCheckBox,
                                        rightCheckBox));
                        topGrid.setWidget(i + 1, 0, button);    
                        topGrid.setWidget(i + 1, 1, leftCheckBox);
                        topGrid.setWidget(i + 1, 2, rightCheckBox);
                }
                toppings.add(topGrid);
                panel.add(toppings, DockPanel.EAST);
        }
        
}

ToppingCallback クラスのインスタンスは、非同期インターフェースによって送信されます。GWT は作業を裏で管理します。そのため、まず適切な呼び出しがサーバーに対して行われ、そのサーバーを呼び出した結果がコールバック・オブジェクトに提供されてから、該当する応答メソッドが呼び出されます。

この場合の失敗オプションは説明が簡単です。サーバー・サイドのメソッドが何らかの理由で例外をスローすると、コールバックの制御が onFailure() メソッドに移ります。このメソッドの引数は、サーバー・サイドが返した例外化、スロー可能なオブジェクトです。この引数を利用すれば、失敗に対する正常な応答を行うことができます。上記で使っているのは、ストリングと引数としてThrowable を取る GWT.log() メソッドです。このメソッドは GWT ホスト・モードの場合、シェル・ウィンドウにログ・メッセージを出力します。ただしメソッドが機能するのはホスト・モードの場合だけなので、Webモードでは無視されます。GWT.log() メソッドは実際には開発中の追跡用に作成されたものですが、更新しようとしていた画面の一部にわかりやすいデフォルトまたはエラー・メッセージとして配置したり、あるいはブラウザー全体をエラー画面にリダイレクトするために使用できます。

連載第 1 回目の記事を読んでいれば、onSuccess() コードが元のバージョンでの buildToppingTypePanel() メソッドとほとんど同一であることにすぐに気付くはずです。その違いの 1 つは些細なことで、上記では配列ではなくリストをループしているため、ループ制御が多少変更されているという点です。また、トッピングの価格もデータベースから取得できるため、表示に追加されています。

大きな違いと言えば、前のバージョンのコードでは、toppings パネルを呼び出し側のコードに返し、呼び出し側のコードが toppings パネルを親パネルに追加することができました。今回のコードでは値を返すことができないため、このメソッドの一部として、新しく作成した toppingsパネルを親パネルに追加しなければなりません。コードを実行すると、最初に Pizza パネルが表示され、その後 GWT がサーバーの呼び出しをシミュレートするため少々遅れてToppings パネルが表示されます。画面は図 1 のようになります。


図 1. 新しいトッピング・パネル

このちょっとした例でさえ、非同期構造への移行によってコードの構造に大きな影響が出てきています。始めからしてすでに、サブパネルがメインパネルに追加される順番が親パネル内での各サブパネルの最終的な形状を決定する上で重要になっています。RPCによる呼び出しに移行する初期化部分が増えるにつれ、サブパネルの作成順には依存できなくなってきます。したがって、パネルの初期作成はウィジェットのパネルへの挿入とは切り離さなければなりません(これによって、新しいデータが追加される際の画面のちらつきを抑えられるという利点もあります)。全体的な目標は、非同期コールバック内のコードをできるだけ単純なものにし、コード同士をできるだけ切り離したものにすることです。

スレッドを互いに依存させると、コードが極めて複雑になってしまいます。


次回の予告

この記事では、GWT を使って単一の RPC を作成するプロセスを辿ってきました。この時点で、正真正銘の GWT アプリケーションが完成していますが、それでもまだこのアプリケーションはホスト・モードの開発マシンでしか実行できません。現実の世界に紹介するには、Webモードに移り、Web アプリケーションをサーブレット環境にデプロイする必要があります。デプロイメントについては、この連載の最終回で説明するのでお楽しみに。


参考文献

学ぶために

製品や技術を入手するために

  • Google Web Toolkitをダウンロードしてください。

  • 無料で入手できる広範なオープン・ソースの EclipseIDE をダウンロードしてください。

  • IBM ソフトウェアの試用版を使用して、次のオープン・ソース開発プロジェクトを革新してください。ダウンロード、あるいは DVD で入手できます。

議論するために

著者について

ジョージア工科大学 Graphics, Visualization, and Usability Center 出身の博士、Noel Rappin は、Motorola 社のシニア・ソフトウェア・エンジニアです。「wxPython in Action」および「Jython Essentials」の共著者でもあります。

不正使用の報告のヘルプ

不正使用の報告

ありがとうございます。 このエントリーは、モデレーターの注目フラグが設定されました。


不正使用の報告のヘルプ

不正使用の報告

不正使用の報告の送信に失敗しました。


developerWorks: サイン・イン


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。 プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。 お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

表示名をお選びください

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

(半角英数字で3文字以上31文字以下にする必要があります)


「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


この記事を評価する

コメント

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source, Information Management, Java technology, Web development
ArticleID=249811
ArticleTitle=Google Web Toolkit、Apache Derby、Eclipse を使用して Ajax アプリケーションをビルドする 第 3 回: 通信
publish-date=02132007
author1-email=noelrappin@gmail.com
author1-email-cc=ruterbo@us.ibm.com