レベル: 初級 Graham Glass (graham-glass@mindspring.com), CEO/Chief Architect, The Mind Electric
2001年 1月 本稿では、SOAPの仕組みについて説明し、特にSOAPのネットワーク用プロトコルや、メッセージの処理方法などを取り上げます。Web Services間で値によるオブジェクトの受け渡しが行われる方法についても解説し、パフォーマンスやセキュリティーの問題にも触れることにします。
進化と革命という観点からWeb Servicesテクノロジーに切り込むこの連載も、今回で3回目になりました。第2回の記事では、Simple Object Access Protocol (SOAP) のApache実装を使って、簡単なWeb Serviceの構築、展開、起動の方法を示したわけですが、今回は、水面下でSOAPがどんな働きをしているのかについて説明します。これは確かに、技術的な観点からして興味の尽きない問題であると同時に、これから取り上げていく将来の規格をしっかり理解するための基盤になります。つまり、Web Services Description Language (WSDL) やUniform Description, Discovery and Integration (UDDI) 規格などについて理解する上で、SOAPは避けて通れない問題なのです (参考文献を参照)。
ツールとインストール作業
今回も、前回の記事で取り上げたのと同じツールを使うので、新しいソフトウェアをインストールする必要はありません。さらに、サンプルも前回の記事と同じものを使うので、\demo1 ディレクトリーに、IExchange.java、Exchange.java、Client.java の各ソース・ファイルが入っていることをまず確認してみてください。
今回は、新しいソフトウェアを1つだけ紹介します。「TCP Tunnel GUI」と呼ぶことにしますが、要するに、クライアントとサーバーの間でTCPメッセージをやり取りする様子を確認するためのツールです。SOAPのネットワーク用プロトコルを調べたり、競争を繰り広げているSOAPの各種実装が実際に規格に準拠しているかどうかをチェックしたりすることさえできます。
SOAPの雰囲気をつかむ
TCP Tunnel GUIは、TCPルーターの働きをするため、SOAPメッセージの "雰囲気" をつかむのに役立つツールです。実際の様子を見るには、まず \demo1ディレクトリーでApache Tomcatサーバー (前回の記事の参考文献を参照) を開始する必要がありますが、そのためには、次のように入力します。
> cd \demo1> tomcat run
Tomcatは、デフォルトではポート8080で起動します。それから、TCP Tunnel GUIを別のウィンドウで開始するわけですが、そのためには、次のように入力します。
> java org.apache.soap.util.net.TcpTunnelGui 8070 localhost 8080
このようにして、TCPサーバーがポート8070で起動します。これが、ローカル・ホストのポート8080とクライアントとの間の中間処理機能としての役割を果たします。この中間処理機能のサーバーになっているのが、このケースではTomcatです。TCP Tunnel GUI (図1 を参照) には、左側のペインに発信メッセージ、右側のペインに着信応答がそれぞれ表示されます。
図1: TCP Tunnel GUI
ここで、前回のサンプルであるClient.java のコードを編集します。Web ServiceのURLを変更して、ポート8080の代わりにポート8070を使うようにしてみましょう。したがって、このクライアント・プログラムの最初の行は、次のようになります。
URL url = new URL( "http://localhost:8070/soap/servlet/rpcrouter" );
このようにしてから、コンパイルして実行してみてください。TCP Tunnel GUIの表示内容を示したのが、図2 です。
図2: TCP Tunnel GUIの実行
お分かりのとおり、左側のペインには、発信のSOAP要求が表示されています。まず、5行の標準的なHTTPヘッダーがあり、その後にクライアント・サービスの呼び出しを記述したXML文書が続いています。それに対して、右側のペインに表示されているのは、結果のSOAP応答であって、冒頭の6行の標準的なHTTPヘッダーの後に、サーバーからの応答を記述したXML文書が来ています。SOAPの形式に関する説明を聞かなくても、見るだけで大体の意味はお分かりでしょう。これとは対照的に、CORBAやDCOMといったプロトコルは、バイナリーであって自己記述形式ではないため、目で追って理解するということが困難です。私自身の経験からしても、その違いはよく分かります。以前はCORBA ORBを記述していたわけですから。
SOAP要求の分析
最初に指摘しておかなければならないのは、今回のセットアップでは、HTTPを使ってSOAPメッセージのやり取りをしていますが、本来、SOAPというものは、どんなトランスポート・プロトコルに乗せてもかまわないという点です。たとえば、SMTPというインターネット電子メール・プロトコルを使って、SOAPメッセージのやり取りをすることも可能です。トランスポート層ごとにヘッダーは違いますが、実際に乗せるXML文書は同じです。分かりやすくするためにXMLの中身も組み込んだ完全なSOAP/HTTP要求がリスト1 にありますので、ご覧ください。
SOAP要求は、HTTP POSTとして送信します。Content-Type はtext/xml に設定し、SOAPAction は空ストリングかSOAPメソッドの名前にしてください。この要求を受け取るWebサーバーでは、SOAPAction の情報から、これが着信のSOAPメッセージであることを認識して、場合によっては、ルーティングやフィルター処理をすることになるわけです。
SOAP要求の中のXML文書は、次の3つの部分から成り立っています。
-
Envelope (エンベロープ)
。ここでは、これ以降のSOAPメッセージで使用する名前空間を定義します。基本的には、xmlns:SOAP-ENV (SOAP Envelope namespace)、xmlns:xsi (XML Schema for Instances)、xmlns:xsd (XML Schema for DataTypes) などの名前空間があります。
-
Header (ヘッダー)
。認証、トランザクション、支払いなどの補足情報を示すための要素ですが、省略してもかまいません。SOAP処理連鎖の中の要素によって、Header の項目を追加したり削除したりすることもできますし、項目が不明であった場合には、その項目を無視するような設定もできます。Header を入れる場合は、必ずEnvelope の最初の子要素にしてください。今回のサンプルは、ルーターを経由しない簡単な要求なので、Header は付けていません。
-
Body (本文)
。メッセージの本体です。SOAPを使ってRPC呼び出しを実行する場合は、Body の中身は1つの要素だけになり、その要素の中に、メソッド名、引数、Web Serviceの宛先アドレスを記述します。この要素の名前空間が要するに宛先アドレスであり、そのベース名がメソッド名になります。このサンプルでは、ns1:getRate と記述していますが、これはつまり、宛先アドレスがurn:demo1:exchange (ns1 を展開した形式) であり、メソッド名がgetRate だという意味になります。Header を入れた場合は、その直後にBody を兄弟要素として記述しなければなりません。そうでない場合は、Envelope の最初の子要素とします。
RPC (リモート・プロシージャー呼び出し) の仕組みとしてSOAPを使う場合は、SOAPパラメーターのデータ型を指定する場合と、指定しない場合とがあります。現行バージョンのApacheの場合は、データ型を指定した引数だけが有効ですが、将来的には、データ型を指定しない引数も使えるようになるようです。デフォルトのSOAPエンコード・スキームでは、xsi:type 属性でいずれかのXSDデータ型を指定することになっています。XSDで定義されている基本的なデータ型としては、int、byte、short、boolean、string、float、double、date、time、URL などがあります。さらに、データ型の不明なデータ・アレイやデータ・ブロックを送信する場合の形式も指定されています。
SOAPは元々、プラットフォームや言語を選ばない仕様なので、XSDでは、言語固有のオブジェクトや構造をエンコードするための形式が定義されていません。本稿の後半では、Apache SOAP 2.0を実行しているマシンどうしで、Javaオブジェクトをやり取りする方法を示します。
SOAP/HTTPメッセージを受信するのは、基本的にWebサーバーで実行されているサーブレットです。このサンプルでは、HTTP要求を受信するサーブレットが、Apache 2.0からTomcatのsoap/servlet/rpcrouter にインストールされます。サーブレットは、要求を受け取った時点で、その要求にSOAPAction フィールドがあるかどうかをチェックして、そのフィールドがあれば、要求をApache SOAPエンジンに転送します。Apache SOAPエンジンは、要求の中に入っているXML文書の構文解析を行ってから、Web Serviceの宛先アドレスを使って、ローカル・レジストリーの中を検索します (ローカル・レジストリーには、始動時にDeployedServices.ds ファイルの内容が読み込まれています)。リフレクションを使って指定のメソッドを見つけたら、そのメソッドをWeb Serviceに対して実行して、結果を得るという流れになります。
次のセクションでは、クライアントに結果を戻すためのSOAP/HTTP応答の形式について解説します。
SOAP応答の分析
SOAP/HTTP応答 (リスト2 を参照) は、標準的なHTTP応答の中に組み込まれたXML文書として戻されます。その場合のContent-Type は、text/xml に設定されます。このXML文書は、要求の場合のXML文書と基本的に同じ構造になっていますが、Bodyの部分に、エンコードされたメソッド結果が入っている点だけが違います。結果の名前空間は元の宛先オブジェクトのURI、ベース名は呼び出されたメソッドの名前になります。XSI/XSDのタグ付けスキームを使って、結果のデータ型を示すこともできますが、これは省略も可能です (参考文献を参照)。SOAP規格では、void メソッドから戻される情報を規定していませんが、ほとんどの実装では、Body の<return> の部分を省略するというかたちにしています。
SOAP例外
メッセージの処理中に例外が発生した場合は、SOAP例外が出されます。SOAP例外は、普通のSOAP応答と基本的に同じ方法でエンコードされますが、唯一の違いは、Body の中に、例外の情報が記述されるという点です。一例として、通貨の両替用のWeb Serviceを記述したExchange.java を編集し、強制的に例外を出すようにしてみましょう (リスト3)。
リスト3: SOAP例外
{
public float getRate( String country1, String country2 )
{
throw new RuntimeException( "cannot calculate rate" );
/*
System.out.println( "getRate( " + country1 + ", " + country2 + " )" );
return 144.52F; // always return the same value for now
*/
}
} |
ここでいったんTomcatを終了してから再始動すると、新しいバージョンのWeb Serviceが使われることになります。Clientプログラムを使って、そのWeb Serviceを呼び出すと、TCP Tunnel GUIからの出力は、図3 のようになります。
図3: SOAP例外が発生したときのTCP Tunnel GUIの出力
標準的なHTTP応答のヘッダーの部分を見ると、500という状況コードで、これが例外であることが明示されています。中身のXML文書には、普通の応答と同じくEnvelope とBody の部分がありますが、Body の内容がFault構造になっている点が違っています。このFault構造のフィールドは、次のとおりです。
-
faultcode
は、障害のタイプを示すコードです。有効な値としては、SOAP-ENV:Client (メッセージの形式が無効)、SOAP-ENV:Server (送信の問題)、SOAP-ENV:VersionMismatch (Envelope 要素の名前空間が無効)、SOAP-ENV:MustUnderstand (header 内容の処理エラー) があります。
-
faultstring
は、人が読めるような障害説明です。
-
faultactor
は、障害ソースのURIを示すフィールドですが、省略してもかまいません。
-
detail
は、障害の詳細を記述したアプリケーション固有のXMLです。
一部のSOAP実装では、最後のdetail要素を使って、リモート例外の情報 (タイプ、データ、スタック・トレースなど) をエンコードするようになっています。要するに、そのような情報に基づいて、同じエラーがあった場合に、クライアント側で自動的に例外を出せるようにしているわけです。そのようにすれば、Remote Method Invocation (RMI) スタイルのリモート例外についても、SOAPを使った実装ができます。もちろん、クライアントとサーバーが同じSOAP実装を使っていない場合は、この機能が自動的にオフになります。
パフォーマンス
このようにして、HTTPとXMLを使ったSOAPメッセージのやり取りの方法を見てきたわけですが、この時点でパフォーマンスの問題を考えるのも興味深いことです。
CORBA、DCOM、RMIでは、引数と戻り値に対してバイナリーのエンコード形式を使っています。しかも、送信側と受信側がいずれもメッセージのコンテキストを完全に理解しているということが前提になっているので、引数の名前やデータ型などのメタ情報のエンコードもしていません。したがって、パフォーマンス的には申し分ないのですが、その一方で、中間処理機能がメッセージを処理するのが難しくなってしまいます。さらに、各システムが別々のバイナリー・エンコード方式を使っているため、相互運用のためのシステムを構築するのも困難です。
その点、SOAPの場合は、XMLを使ってメッセージをエンコードするので、呼び出し処理のどの段階を取ってみても、メッセージの処理が簡単にできます。しかも、SOAPメッセージはデバッグが容易なため、各種のSOAP実装を束ねるような作業にもそれほど手間がかかりません。大規模な相互運用こそがSOAPの主眼であることを考えれば、この点はたいへん重要です。
表面的なとらえ方をすれば、XMLベースのスキームは、バイナリー・ベースのスキームよりも本質的に速度が落ちるように思えるでしょうが、事はそれほど単純ではありません。第1に、SOAPを使ってインターネット経由でメッセージをやり取りする場合は、エンドポイント間で実際にデータを転送するのにかかる時間に比べれば、各エンドポイントでメッセージをエンコード/デコードするための時間はごくわずかです。つまり、このような場合にXMLを使っても、速度はそれほど落ちないのです。
第2に、SOAPを使って閉鎖的環境にあるエンドポイント (社内の各部門など) の間でメッセージをやり取りする場合は、おそらく、各エンドポイントで同じSOAP実装を使うことになるでしょう。そうであれば、それぞれの実装に合わせて、個別にパフォーマンスの最適化を設定できます。たとえば、まずSOAPクライアントで、SOAP要求にHTTPのheader タグを追加して、サポートする最適化の内容を指定するわけです。SOAPサーバーの側でもその最適化をサポートしていれば、やはり最初のSOAP応答の中にHTTPのheader タグを追加して、これ以降の通信ではその最適化を使ってもOKだということをクライアントに通知します。そのようになった時点で、クライアントとサーバーの両方でその最適化を使い始めるというわけです。
私自身もApacheでいろいろ試してみましたが、同一マシンのJavaプログラム間で、普通は1秒間に約30のメッセージ往復を実行できます。ところが、別のSOAP実装の評価をしていたところ、同じ設定で1秒間に約700のメッセージ往復を実現できました。確かに、改善の余地がかなりあるのは間違いありません。
オブジェクトの受け渡し
最初は簡単なWeb Serviceを構築して展開するだけで興奮していた開発者も、その熱がさめると、今度はクライアントとサーバーの間で実際にオブジェクトのやり取りをしてみたいと思うようになります。そのための取り組み方が少なくとも2つあります。
まず、クライアントとサーバーの両方を同じベンダーの独自機能で設定するという場合ですが、その場合には、マシン間のオブジェクトの受け渡しの方法を何でも自由に選べます。たとえば、クライアントとサーバーの両方がJavaプログラミング言語を実行している場合は、Javaのシリアル化機能を使ってオブジェクトのやり取りをするというふうに決めたりするわけです。
一方、クライアントとサーバーがどんな組み合わせであってもうまく機能するようなシステムを構築したい場合は、まずやり取りするオブジェクトのためのXMLスキーマを選定することから始めます。次に、そのスキーマに準拠したXML文書を、クライアントとサーバーの両方から認識できるように設定し、必要に応じてオブジェクトとスキーマの間の変換ができるようにしなければなりません。たとえば、C++ クライアントがJavaサーバーにオブジェクトを送信するのであれば、クライアント側では、選定したスキーマとC++ オブジェクトとの間で変換ができなければなりませんし、サーバー側では、やはりそのスキーマとJavaオブジェクトとの間で変換ができなければなりません。
Apache SOAP 2.0には、SOAPエンジン間でオブジェクトを受け渡すための柔軟なスキームが、特別なマッピング・レジストリーというかたちで組み込まれています。このレジストリーには、データのエンコード/デコードのための独自のシリアル化機能/非シリアル化機能を登録できます。現在の資料では、独自のシリアル化機能を作成するための方法がほとんど触れられていませんが、うれしいことに、JavaBean仕様に準拠したクラスのシリアル化/非シリアル化を実行できる便利なデフォルトのJava BeanSerializerが用意されています。このツールでは、共通のデフォルト・コンストラクターを組み込み、すべての属性のための共通のset/getメソッドを宣言する必要があります。
オブジェクトのやり取りの方法を示す例として、1つのプログラムを取り上げましょう。このプログラムでは、PurchasingというWeb ServiceがTomcatサーバー上に展開された時点で、ClientがInvoiceオブジェクトを値として渡します。そのオブジェクトは、サーバーに届いた時点で表示され、クライアントに戻された時点で再び表示されます。SOAPでは、参照によってオブジェクトの受け渡しを行うこともできます。その点については、この連載で今後取り上げることになっています。
リスト4からリスト7までが、このPurchasingというWeb Serviceのサンプル・ソース・コードです。
リスト4. IPurchasing.javaのコード
public interface IPurchasing
{
Invoice receive( Invoice invoice );
}
|
リスト5. Purchasing.javaのコード
public class Purchasing implements IPurchasing
{
public Invoice receive( Invoice invoice )
{
System.out.println( "got invoice " + invoice );
return invoice;
}
}
|
リスト6. Invoice.javaのコード
public class Invoice
{
String name;
int amount;
public Invoice()
{
}
public Invoice( String name, int amount )
{
this.name = name;
this.amount = amount;
}
public String toString()
{
return "Invoice( " + name + ", " + amount + " )";
}
public void setName( String name )
{
this.name = name;
}
public String getName()
{
return name;
}
public void setAmount( int amount )
{
this.amount = amount;
}
public int getAmount()
{
return amount;
}
} |
リスト7. Client2.javaのコード
このプログラムを実行するための手順は、次のとおりです。
- 以下のJavaファイルを
\demo2 という新しいディレクトリーに追加し、その\demo2 をCLASSPATH に追加します。
- ソース・ファイルをコンパイルします。
- 前に述べた方法でTCP Tunnel GUIを実行し、ポート8070からの受信を待機しながら、ポート8080との間でルーティングを行います。
-
\demo2 ディレクトリーでTomcatを開始します。
- ブラウザーを開いて、URL
http://localhost:8080/soap と入力します。
- 管理インターフェースを使って、PurchasingというWeb Serviceを展開します。以下の値を入力してください。
-
ID=urn:demo2:purchasing
-
Scope=Request
-
Methods=receive
-
Provider Type=Java
-
Provider Class=Purchasing
-
Static=No
-
Number of Mappings=1
-
Encoding Style=SOAP
-
Namespace URI=urn:my_encoding
-
Local Part=Invoice
-
Java Type=Invoice
-
Java To XML Serializer=org.apache.soap.encoding.soapenc.BeanSerializer
-
XML To Java Deserializer=org.apache.soap.encoding.soapenc.BeanSerializer
- クライアントを実行します。
Apache SOAPエンジンは、xsi:type 属性の値として、Namespace URI とLocal Part の値を使います。これらの値は、他のマッピングと衝突しない限り、それほど重要ではありません。
すべてがうまくいった場合は、TCP Tunnel GUIに図4 のような結果が表示され、クライアントの出力は図5 のようになります。
図4: PurchasingというWeb Serviceの実行
図5: PurchasingというWeb Serviceのクライアント出力ウィンドウ
セキュリティー
セキュリティーは、複雑な問題です。今のところ、SOAPのセキュリティーとしては、階層化の方法が採用されているようです。
一番下のレベルとしては、SOAPメッセージをHTTPSに乗せて受け渡すという方法が可能です。HTTPSでは、トランスポートとしてSSLを使うので、メッセージの内容がエンコードされることになり、いわゆる盗み見の危険は防止できます。また、クライアントとサーバーは、互いの身元を確認できます。SSLに基づいたSOAPソリューションは今でも出回っていますが、インストールと設定の作業はそれほど簡単ではありません。
HTTPSでは、メッセージの盗み見という問題は解決できるものの、個々のWeb Serviceのユーザー認証で必要になるような、さらにきめの細かいセキュリティーについてはあまり期待できません。最初の登録時にある種のユーザー名/パスワードを取得し、それ以降は必ずその認証情報を使うことをユーザーに義務付けるようなWeb Serviceが多くなることでしょう。IBM、Microsoft、Aribaがオンラインで提供しているUDDI Web Servicesレジストリーは、ユーザーが配信サービスを使う前にまず登録を必要とするWeb Serviceの例です。今のところ、Web Serviceで登録と認証をサポートするための規格は存在しませんが、近い将来にそのような規格が登場するのは間違いないでしょう。MicrosoftとVerisignは最近になって、XML Key Management Specification (XKMS) という規格を策定する作業を始めたことを発表しました。その規格は、SOAPなどのシステムに組み込めるように、規格制定団体に提出されることになるようです (参考文献を参照)。
私としても、この連載でまたいつかセキュリティーの問題を取り上げてみたいと思っています。その時には、セキュリティーに関する規格がかなり進んでいることを期待する次第です。
次回の予告
次回の記事では、WSDL (Web Services Description Language) とUDDI (Universal Description, Discovery and Integration) について取り上げる予定です。この2つは、まさにWeb Servicesテクノロジーを時代の主流に押し上げる働きをする重要な規格です。
参考文献
著者について  | |  | Graham Glass氏はThe Mind Electric の創設者、CEO、主任設計士で、大規模分散コンピューティングの構築を専門としています。インターネットの進化は頭脳の進化を反映し、人々や企業が効率的にネットワークを形成するのを 助けるアーキテクチャーは、人々の頭脳をネットワークで結ぶアーキテクチャーにヒントを与えると 氏は考えています。
The Mind Electricの創設以前は、ダラスに本拠を置く、企業間統合に取り組むObjectSpace の会長、CTO、共同創設者でした。ObjectSpaceでは、Voyager製品ラインの設計者兼主任開発者として
分散コンピューティング、JGL Javaコレクション・ライブラリー、さらにクロス・プラットフォームのC++ ツールキットを手がけました。1996年にErnst and Young Entrepeneur年間賞を 受賞したのをはじめ、VoyagerおよびJGLの産業界の賞をいくつも受賞しています。
Graham Glass氏はまた、最先端テクノロジーのトレーニングを提供するObjectLesson社の創設者でした。UNIXおよびSTLに関するPrentice Hallの2冊の書籍を著作し、新しいテクノロジーに関する熱意にあふれた分かりやすい話をする 講演者としても知られています。
University of Southamptonで 数学およびコンピューター・サイエンスのBSc、University of Texasでコンピューター・サイエンスのMS、およびHaberdashers' Aske's Schoolで 英国 "O" レベルと "A" レベルをそれぞれ取得しました。産業界に身を転じる以前は、UTDで上級講師としてUNIX、C、C++、Smalltalk、およびプログラム言語を教えました。連絡先はgraham-glass@mindspring.com です。 |
記事の評価
|