ヒント

Webサービスにファイルを渡す

SOAPとバイナリ・データ

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: ヒント

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:ヒント

このシリーズの続きに乞うご期待。

Webサービス・プロトコルは、簡単なパラメータがあるだけの非常に単純なリクエストのサポートから、最新のオブジェクト指向言語を完全にサポートするところまで大きく進化しました。XML-RPCはおそらく、Webサービスの最も初期の形態の一つですが、文字列や整数、ブール値など、単純な型しかサポートしませんでした。SOAPは、さらにもう一歩踏み込んで、オブジェクトに対してのエンコード規則を備えるようになりました。その最後の段階・・・バイナリに対する改良・・・が、アタッチメント付きSOAP (SOAP with attachments)です。

アタッチメント付きSOAPは元々SOAP 1.1の拡張として導入され、主なSOAPキットでサポートされています。W3Cからの正式なリリースであるSOAP 1.2はまだアタッチメント(添付)をサポートしていませんが、(理想ですが・・・)近い将来にサポートすべく作業が行われています。

Webサービスとバイナリ・データ

XMLがアプリケーション統合で成功したのは、(オブジェクト指向RPC標準であるCORBAやJava特有のRPC標準であるRMIなどのようなバイナリのプロトコルではなく)テキスト型エンコーディング(textual encoding)に依存しているという点にあることはまず疑いないと言えるでしょう。テキスト型エンコーディングが有利な理由はいくつかありますが、最も重要なものとしてはデバッグが容易なこと、また必要に応じて特別な実装を容易に持ち込めることが挙げられます。

ただし一方では、テキスト型エンコーディングに依存することによる問題もあり、バイナリ・データをどう含めるかについてXMLは何ら効率的な方策を用意していないのです。W3CのXML Schema仕様によると、バイナリ・データは、Base64または、16進でエンコードすべきだ・・・、となっています。残念ながら、Base64でエンコードされたデータは、エンコードしていないデータよりも 50% も大きいのです。16進では、サイズが2倍になります。小さなバイナリ・データであれば、このオーバーヘッドも問題になりませんが、大きなデータに対しては問題となることは明らかです。

バイナリ・データは多くのアプリケーションにとって有用なものです。例えば・・・

  • セキュリティ関連のアプリケーションでは、キーやハッシュ、証明書、また暗号化されたデータそのものが必要です。
  • マルチメディア関連のアプリケーションでは、写真や、音楽、または動画などを扱います。
  • 一部のアプリケーションでは、XML表記でのデータでは効率が悪すぎる場合があります。CAD/CAMを考えてみてください。
  • 何千ものファイル形式がXML以前に存在しています。ワープロ、表計算、フォント、ベクタ・グラフィックス、系統図・・・、その他無数のファイル形式です。

こうしたファイル形式の(ベクタ・グラフィックスに対するSVGのような)XML版を作成することも可能ですが、バイナリ・データは長年に渡って使われており、おそらく今後も一般的なものとして残るでしょう。

最後に、XML自体の問題もあります。XML文書の中に別のXML文書を含めることは簡単ではないのです(構文的に適切な方法としては、CDATAセクションと別扱い文字(character escaping)を使用することになります)。

こうしたアプリケーション全ての要求に応えるために、Webサービスは効率的にバイナリ・データをサポートする必要があります。そのために提案されている方法が、アタッチメント付きSOAPですが、簡単に言ってしまえばこれはXMLペイロードからバイナリ情報を取り除き、その情報をmultipart/related MIMEコンテンツとして、直接HTTPリクエストに格納すると言うことです。

バイナリ・データを扱うWebサービスを設計する際の選択肢として、次のようなものがあります。

  • データセットが小さければ、XMLペイロード内でBase64エンコーディングを使用する。小さなデータセットであれば、オーバーヘッドは大した問題になりません。
  • データセットが大きければ、現実的な選択肢としては添付しかありません。

リスト1は、Base64でエンコードされたパラメータを持つSOAPリクエストです。address要素に注目してください。

リスト1. Base64でエンコードされたパラメータ
POST /ws/retrieve HTTP/1.0
Content-Type: text/xml; charset=utf-8
Accept: application/soap+xml multipart/related, text/*
Host: localhost:8080
SOAPAction: ""
Content-Length: 540

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <soapenv:Body>
  <ps:retrieve 
           soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
           xmlns:ps="http://psol.com/2004/ws/retrieve">
   <address xsi:type="xsd:base64Binary">d3d3Lm1hcmNoYWwuY29t</address>
  </ps:retrieve>
 </soapenv:Body>
</soapenv:Envelope>

添付を実装する

Java開発者は、JAX-RPC (Java API for XML-based RPC)を使っても、SAAJ (SOAP with Attachments API for Java)を使っても添付を使うことができます。SAAJにだけAttachmentsの頭文字があるからと言って、SAAJだけが添付をサポートしているわけではありません。JAX-RPCも添付をサポートしているのです(参考文献に例があります)。JAX-RPCとSAAJの違いは抽象化のレベルであって、機能の差ではありません。

JAX-RPCはSAAJよりもさらに抽象化を高めた高位のAPIであり、SOAPの持つ、プロトコル指向の側面の大部分をRMIレイヤーの陰に隠します。開発者がJavaオブジェクトに対して操作すると、プリ・プロセッサがそのJavaオブジェクトをSOAPノードに変えます。JAX-RPCは、添付を表現するためにjava.awt.Imagejavax.activation.DataHandlerクラスを使います。

SAAJは、プロトコル(のレベル)により近いものです。SAAJでSOAPメッセージを作る方が、JAX-RPCで作るよりも手間がかかります(その上、WSDLへの自動リンクも提供しません)。ですからたいていの場合、皆さんはJAX-RPCを使いたいと思うでしょう。ただし、SAAJの方が低位である分、添付が実際にどのように動作するのかをより明確に表現できます。リスト2は添付を持つSOAPリクエストです。このリクエストは、サーバに対して写真のサイズを変更するように要求しています。写真ファイルは大きいので、添付の方がより効率的です。

リスト2. 添付パラメータ
POST /ws/resize HTTP/1.0
Content-Type: multipart/related; type="text/xml"; 
     start="<EB6FC7EDE9EF4E510F641C481A9FF1F3>"; 
     boundary="----=_Part_0_7145370.1075485514903"
Accept: application/soap+xml, multipart/related, text/*
Host: localhost:8080
SOAPAction: ""
Content-Length: 1506005

------=_Part_0_7145370.1075485514903
Content-Type: text/xml; charset=UTF-8
Content-Transfer-Encoding: binary
Content-Id: <EB6FC7EDE9EF4E510F641C481A9FF1F3>

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <soapenv:Body>
  <ps:resize 
          soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
          xmlns:ps="http://psol.com/2004/ws/resize" 
          xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
   <source href="cid:E1A97E9D40359F85CA19D1B8A7C52AA3"/>
   <percent>20</percent>
  </ps:resize>
 </soapenv:Body>
</soapenv:Envelope>

------=_Part_0_7145370.1075485514903
Content-Type: image/jpeg
Content-Transfer-Encoding: binary
Content-Id: <E1A97E9D40359F85CA19D1B8A7C52AA3>

note: binary data deleted...

------=_Part_0_7145370.1075485514903--

リスト3にSOAPリクエストの生成過程を示します。リクエストは、サーバに対して画像のサイズを変更するように要求しています。手順は次の通りです。

  • Factoryを使ってSOAPコネクションとSOAPメッセージ・オブジェクトを生成する。
  • メッセージ・オブジェクトからメッセージ・ボディを取得する(中間ステップ: SOAPパートとエンベロープを取得する)。
  • リクエストを表す新しいXML要素を生成し、エンコーディング形式を設定する。
  • 添付を生成し、それを引数としてDataHandlerオブジェクトを初期化する。
  • さらに、2つのパラメータ(sourcepercent)を表す要素を生成する。
  • href属性を追加し、最初のパラメータ(source)に添付を関連付ける。添付は、cid (content-id) URLで参照される。
  • 2番目のパラメータ(percent)の値を直接テキストとして設定し、サービスを呼び出します。

サービスは、サイズ変更した画像を(再び添付として)返します。結果を取り出すために、SOAPフォルトを確認します(これでエラーが分かります)。失敗がなければ、添付をファイルとして取り出し、必要な処理を行います。

リスト3. SAAJを使う
public File resize(String endPoint,File file)
{
   SOAPConnection connection =
      SOAPConnectionFactory.newInstance().createConnection();
   SOAPMessage message = MessageFactory.newInstance().createMessage();
   SOAPPart part = message.getSOAPPart();
   SOAPEnvelope envelope = part.getEnvelope();
   SOAPBody body = envelope.getBody();
   SOAPBodyElement operation =
      body.addBodyElement(
         envelope.createName("resize",
                             "ps",
                             "http://psol.com/2004/ws/resize"));
   operation.setEncodingStyle("http://schemas.xmlsoap.org/soap/encoding/");

   DataHandler dh = new DataHandler(new FileDataSource(file));
   AttachmentPart attachment = message.createAttachmentPart(dh);
   SOAPElement source = operation.addChildElement("source",""),
               percent = operation.addChildElement("percent","");
   message.addAttachmentPart(attachment);
   source.addAttribute(envelope.createName("href"),
                       "cid:" + attachment.getContentId());
   width.addTextNode("20");

   SOAPMessage result = connection.call(message,endPoint);
   part = result.getSOAPPart();
   envelope = part.getEnvelope();
   body = envelope.getBody();
   if(!body.hasFault())
   {
      Iterator iterator = result.getAttachments();
      if(iterator.hasNext())
      {
         dh = ((AttachmentPart)iterator.next()).getDataHandler();
         String fname = dh.getName();
         if(null != fname)
            return new File(fname);
      }
   }
   return null;
}

添付がXMLメッセージの外側にあることが、リスト3を見れば明確であることに注意してください。これは効率性のために必要です。

効率と言うことであれば、リスト4を見てください。こちらはより一般的な(そして劇的に短い)、リスト3のJAX-RPC版です。JAX-RPCプリコンパイラは、コーディングを大幅に単純化するスタブを生成します。DataHandlerオブジェクトをメソッドのパラメータとして渡せば、JAX-RPCが自動的に添付を生成します。

リスト4. より効率的なJAX-RPC
public File resize(File file)
   throws ServiceException, RemoteException
{
   AttachmentService service = new AttachmentServiceLocator();
   AttachmentTip port = service.getAttachmentTip();   // get stub
   DataHandler dh = new DataHandler(new FileDataSource(file));
   DataHandler result = port.resize(dh,20);
   return new File(result.getName());
}

まとめ

選択肢があるのは良いことです。そしてSOAPには、バイナリを取り扱う際に選択肢があるのです。XMLペイロードの中でBase64としてエンコードするか(小さなデータセットには適当です)、またはエンコードせずに、大きなバイナリ・ファイルをリクエストに添付することもできるのです。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, SOA and web services
ArticleID=242467
ArticleTitle=ヒント: Webサービスにファイルを渡す
publish-date=02132004