ObjectSquare [2003 年 5 月号]

[技術講座]


Axis .NET 連携

株式会社 オージス総研
オブジェクトテクノロジーソリューション 第二開発部
内山 博之

1. はじめに

 Web サービスを使ったシステムは .NET と J2EE の両プラットフォームの連携が可能です。そこで、.NET リッチクライアントと J2EE サーバによるシステム構成について調べてみました。2章では一般的なシステムアーキテクチャについて解説し、3章では .NET と J2EE を連携するためのサンプルを作成しています。実際にこの構成でシステムを開発すると通信に使われるデータ量が多く、通信部分がボトルネックの一つになってしまいます。そこで、4章では通信部分でパフォーマンスを上げるために行った工夫をご紹介します。


2. SOAPを使った一般的なシステムアーキテクチャ

 アプリケーションサーバを使用したシステム構成では、EIS (Enterprise Information System) 層、ビジネスロジック層、プレゼンテーション層という三層構成にするのが一般的です。EIS 層には DB サーバなどが相当し、ビジネスロジック層にはアプリケーションサーバが相当します。アプリケーションサーバ上にはビジネスロジックが実装されます。プレゼンテーション層には Web サーバが相当し、HTML を表示する役目を果たします。最近では J2EE の信頼性も世間に浸透し、J2EE を使ったシステム開発はごく普通になってきました。このようなシステム構成の場合クライアントは一般的にWeb ブラウザでしょう。

 ところで、.NET Framework の登場によりクライアントサイドアプリケーションのリッチクライアント化が復活の兆しを見せていますが、システム構築の際にリッチクライアントを選択するということの利点はなんなのでしょうか? 第一に、操作性の向上です。特に入力量が多いシステムでは、データ入力時のクライアントの操作性による作業効率への影響は大きなものがあるでしょう。例を挙げますと、一般にブラウザではファンクションキーの設定は難しいですし、HTML では表示できないコントロールもいくつかあります。第二に、クライアントサイドで実装したほうがよい機能が実装できる点です。ロック機能などのセキュリティを向上するための仕組みや、ログ機能の実装はクライアントアプリケーション単体では難しいでしょう。以上のように、ビジネスロジックはサーバへ残したまま、ブラウザでは物足りないクライアントの機能を補完することができるのです。

 では、J2EE サーバ + リッチクライアントの構成を採るシステムで、両者はどのように接続すべきでしょうか? 候補としては Socket、CORBA などがありますが、以下の理由から Web サービスを選択します。

 そこで、J2EE サーバと Visaul Basic .NET クライアントでの Web サービスによる相互連携について解説します。J2EE サーバには SOAP エンジンとして Apache AXIS を使用します。

 Web サービスとは様々な意味で使われますが、技術的な側面から見ると、SOAP、WSDL、UDDI という標準技術を軸に構築される分散システム環境です。リッチクライアントなシステムは多くの場合インターネットに公開するサービスではなく、組織内部で使用するためのサービスでしょう。したがって WSDL を定義するだけで、UDDI は使用しません。WSDL で Web サービスのインタフェースを定義し、通信を SOAP で行います。

 ご存知のとおり SOAP とは XML でオブジェクトを表現しネットワーク越しにオブジェクトを受け渡すために規定されているプロトコルです。下位プロトコルには SMTP や FTP なども使用できますが、現在は HTTP 上で SOAP を使用することが圧倒的に多いようです。では、HTTP を下位プロトコルとして使用した SOAP 通信のメカニズムを見てみましょう。想定する Web サービス実行環境はサーバに J2EE サーバ、クライアントに .NET によるリッチクライアントです。

クリックすると大きな画像が参照できます

 SOAP 通信には大きく分けてシリアライズを行うステップと通信を行うステップという二つのステップがあります。上図のシーケンス番号ごとに説明を行いますと、

  1. クライアントアプリケーションがサーバへの送信オブジェクトを作成し、オブジェクトを SOAP 通信処理に渡す。SOAP 通信処理に渡されると、まず最初に SOAP メッセージにシリアライズします。この時点で .NET Framework 上のオブジェクトからテキストで記述された SOAP メッセージになります。
  2. テキストである SOAP メッセージは HTTP により送信されます。
  3. 送信された SOAP メッセージはテキスト(XML)の状態でネットワークを通りサーバへ渡されます。HTTP に乗ってファイアウォールなど関係なくサーバへたどり着きます。
  4. サーバで受信された SOAP メッセージは XML から Java のオブジェクトへデシリアライズされます。
  5. デシリアライズされた Java のオブジェクトはアプリケーションサーバのビジネスロジック層へ渡され、ビジネスロジックの処理が行われます。
  6. ビジネスロジックの処理が終了し、クライアントへ返すオブジェクトは SOAP 通信を行うために Java のオブジェクトから、テキストベースの SOAP メッセージへシリアライズされます。
  7. シリアライズされた SOAP メッセージは HTTP のレスポンスによりクライアントへ送信されます。
  8. 4. と同様に送信された SOAP メッセージはテキスト(XML)の状態でネットワークを通りクライアントへ渡されます。
  9. クライアントは SOAP メッセージを受信し、XML から .NET Framework のオブジェクトにデシリアライズします。
  10. サーバから返された値を .NET Framework 上のオブジェクトとしてクライアントアプリケーションで処理し、表示などを行います。

 以上が .NET と J2EE 間の SOAP 通信の大まかな流れです。上記の一連の流れの中に SOAP メッセージへのシリアライズがあります。この処理により、オブジェクトを Java、.NET 両プラットフォームが共通に理解できる SOAP メッセージに変換し、プラットフォーム間の違いを吸収しているのです。

シリアライズ

 ここで SOAP のシリアライズについて説明します。一般的に、シリアライズとはメモリ上のデータをファイルなどに保存できる形式に変換することを言います。デシリアライズとはその逆で、ファイルなどに保存されたデータ形式から、メモリ上のオブジェクトに変換することを言います。SOAP メッセージへのシリアライズも同様でメモリ中のオブジェクトをあるルールに従ってテキストに変換します。では、あるルールとはどのようなものでしょうか?

SOAP メッセージは以下のような構成になっています。

<SOAP-ENV:Envelope
    xmlns:SOAP-ENV="https://schemas.xmlsoap.org/soap/envelope/"
    SOAP-ENV:encodingStyle="https://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header/>
  <SOAP-ENV:Body>
    <!-- オブジェクトの情報をセットします。 -->
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

 Envelope が最も外側の要素として定義され、その中に Header 要素と Body 要素が存在します。Header 要素は省略可能で、主に SOAP の機能拡張用に使用されます。Body 要素は省略不可で、実際受け渡しされるオブジェクトについての情報が子要素としてセットされます。Web サービスでは子要素としてセットされるオブジェクト情報の記述の仕方に、RPC 指向とDocument 指向とがあります。RPC 指向と Document 指向どちらで Web サービスを実行するかはサービスプロバイダの WSDL に記述されています。サービスリクエスタはその記述に従いシリアライズを行わなければなりません。

 では、RPC 指向、Document 指向とは実際どのようなものでしょうか?

RPC 指向

 RPC 指向では送信するオブジェクトデータはメソッドの引数としてシリアライズされます。具体的に Body 要素は以下のようになります。

<SOAP-ENV:Body>
  <tns:service>
    <request href="#id1" />
  </tns:service>
  <tns:Request id="id1" xsi:type="tns:Request">
    <name xsi:type="xsd:string">myName</name>
  </tns:Request>
</SOAP-ENV:Body>

Document 指向

 Document 指向では送信するオブジェクトデータをそのまま SOAP Body 要素の子要素としてシリアライズします。具体的に Body 要素は以下のようになります。

<SOAP-ENV:Body>
  <tns:Request id="id1">
    <name xsi:type="xsd:string">myName</name>
  </tns:Request>
</SOAP-ENV:Body>

 RPC 指向では RPC のメソッド名を特定するために service というメソッド名のタグが記述されています。それと比較すると Document 指向では RPC の引数だけを SOAP Body 要素に記述しています。RPC 指向の SOAP メッセージの記述方法は SOAP1.1 の仕様書のセクション7に記述されているため、J2EE サーバでは Document 指向より RPC 指向の方がサポートされているようです。ちなみに .NET Framework での SOAP メッセージの既定の設定は Document 指向になっています。

 また、SOAP メッセージの Body 要素の子要素にセットされるオブジェクト情報のエンコーディング方法にも二通りの指定の仕方があります。Literal と Encoded です。

Literal

 サービスプロバイダとリクエスタ間で通信するデータのスキーマを定義することにより、エンコーディングをその Web サービス固有のものにします。したがって、簡潔な記述が可能で、データ量も少なくなります。

Encoded

 一方 Encoded と呼ばれるエンコーディング方法は SOAP 仕様書のセクション5に記述された記述方法で、Literal に比べ少し複雑な記述方法になりますが、一般的です。

 これらのルールに従ってシリアライズを行い、HTTP の通信を行って Web サービスを実行します。.NET では Document/Literal 形式を推進しているようですが、現在のところ RPC/Encoded で Web サービスを行うことが多いようです。


3. J2EE と .NET の SOAP 通信

 では、簡単なサンプルアプリケーションを作成してみます。作成するアプリケーションはWindows フォーム上のテキストボックスに文字を入れてボタンコントロールを押すと、その文字に「Hello!」がくっついて戻り、その文字をラベルコントロールに表示するという単純なものです。RPC/Encoded で Web サービスを実行します。

 なお、Axis はバージョン 1.0 、.NET Framework もバージョン 1.0 を使用します。

 大まかな作成手順は以下のようになります。

(1) Axis を使って Web サービスを実装する
(2) WSDL を取得し、クライアント側の Proxy クラスを作成する
(3) クライアント側のアプリケーションを実装する

 以上の3ステップです。

 では、Axis に Web サービスを実装してみましょう。

(1) Axis に Web サービスを実装する

 まず、定義しなければならないクラスとして以下のものがあります。

実際にサービスを実行するクラス
具体的にはクライアントから受け取った文字に「Hello!」をくっつけて戻すメソッドを持ったクラスです。
package sample.service;

import sample.beans.Request;
import sample.beans.Response;

// Webサービスを実行するためのクラス
public class HelloService {

    // Webサービスとして呼び出されるメソッド
    public Response service(Request request) {
        Response response = new Response();
        response.setReturnValue("Hello! " + request.getName());
        return response;
    }
}
クライアントとサーバ間でやり取りする Bean クラス
ここではサーバとクライアント間でのデータのやり取りを String などの基本型でなく、 Request クラスと Response クラスという二つのBeanクラスを用意して、そのクラスを使って Web サービスを実行します。
package sample.beans;

// クライアントからサーバへ送信するクラス
public class Request {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
package sample.beans;

// サーバからクライアントへ送信するクラス
public class Response {

    private String returnValue;

    public String getReturnValue() {
        return returnValue;
    }

    public void setReturnValue(String returnValue) {
        this.returnValue = returnValue;
    }
}

 Request クラスの name 属性から String を読み取り「Hello!」を付加して Response の returnValue 属性に渡すというのが一連の流れです。このサービスを Axis に配置するために、deploy.wsdd を記述し、デプロイを行います。

<deployment
    xmlns="https://xml.apache.org/axis/wsdd/"
    xmlns:java="https://xml.apache.org/axis/wsdd/providers/java"
    xmlns:sample = "urn:sample">

  <globalConfiguration>
    <!-- 単一参照形式でクライアントへデータ送信 -->
    <parameter name="sendMultiRefs" value="false"/>
  </globalConfiguration>

  <service name="HelloService" provider="java:RPC">
    <!-- 名前空間 -->
    <namespace>urn:sample</namespace>
    <!-- Webサービスとして配置されるクラス -->
    <parameter name="className" value="sample.service.HelloService"/>
    <!-- Webサービスとして配置されるメソッド -->
    <parameter name="allowedMethods" value="service"/>
    
    <!-- クライアントからサーバへ送信されるリクエスト -->
    <beanMapping
        qname="sample:Request"
        languageSpecificType="java:sample.beans.Request"/>

    <!-- サーバからクライアントへ送信されるレスポンス -->
    <beanMapping
        qname="sample:Response"
        languageSpecificType="java:sample.beans.Response"/>

    <!-- SOAPメッセージの出力形式を指定するために設定 -->
    <operation
        name="service"
        returnQName="ns:Response"
        returnType="ns:Response"
        xmlns:ns="urn:sample">
      <parameter
          name="request"
          type="tns:Request"
          xmlns:tns="urn:sample"/>
    </operation>

  </service>

</deployment>

 deploy.wsdd の記述を説明します。deployment 要素は globalConfiguration 要素と service 要素を含んでいます。globalConfiguration 要素で設定する項目は、デプロイするサービス全般に共通に設定する項目です。service 要素では実際に Axis に実行させる Web サービスを設定します。サーバを起動し、deploy.wsdd を org.apache.axis.client.AdminClient コマンドによりデプロイします。成功すれば、サーバに HelloService という Web サービスが公開されます。AdminClient を使うためにはサーバで Axis が起動していなければなりません。

(2) WSDL を取得し、クライアント側の Proxy クラスを作成する

 公開された Web サービスを確認するために Axis が動作しているサーバの AxisServlet をブラウザで表示します。

 AdminService と Version と HelloService が公開されていることがわかります。ここから HelloService の wsdl を取得し、クライアントの SOAP 通信の Proxy クラスを作成します。Proxy クラスは .NET Framework の wsdl ツールによりコマンド一つで自動生成できます。今回はクライアントを Visual Basic .NET で作成します。Visual Studio .NET ツールの「Visual Studio .NET コマンドプロンプト」で以下のコマンドを実行します。

wsdl /l:vb HelloService.wsdl

 生成されたコードは、HelloServiceService.vb で以下のようになります。

'------------------------------------------------------------------------------
' <autogenerated>
'     This code was generated by a tool.
'     Runtime Version: 1.0.3705.288
'
'     Changes to this file may cause incorrect behavior and will be lost if 
'     the code is regenerated.
' </autogenerated>
'------------------------------------------------------------------------------

Option Strict Off
Option Explicit On

Imports System
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Xml.Serialization

'
'このソース コードは wsdl によって自動生成されました。Version=1.0.3705.288 です。
'

'<remarks/>
<System.Diagnostics.DebuggerStepThroughAttribute(), _
 System.ComponentModel.DesignerCategoryAttribute("code"), _
 System.Web.Services.WebServiceBindingAttribute( _
        Name:="HelloServiceSoapBinding", [Namespace]:="urn:sample")> _
Public Class HelloServiceService
    Inherits System.Web.Services.Protocols.SoapHttpClientProtocol

    '<remarks/>
    Public Sub New()
        MyBase.New()
        Me.Url = "https://localhost:8080/axis/services/HelloService"
    End Sub

    '<remarks/>
    <System.Web.Services.Protocols.SoapRpcMethodAttribute("", _
            RequestNamespace:="urn:sample", ResponseNamespace:="urn:sample")> _
    Public Function service(ByVal request As Request) As _
            <System.Xml.Serialization.SoapElementAttribute("Response")> Response
        Dim results() As Object = Me.Invoke("service", New Object() {request})
        Return CType(results(0), Response)
    End Function

    '<remarks/>
    Public Function Beginservice(ByVal request As Request, _
            ByVal callback As System.AsyncCallback, _
            ByVal asyncState As Object) As System.IAsyncResult
        Return Me.BeginInvoke("service", New Object() {request}, callback, asyncState)
    End Function

    '<remarks/>
    Public Function Endservice(ByVal asyncResult As System.IAsyncResult) As Response
        Dim results() As Object = Me.EndInvoke(asyncResult)
        Return CType(results(0), Response)
    End Function
End Class

'<remarks/>
<System.Xml.Serialization.SoapTypeAttribute("Request", "urn:sample")> _
Public Class Request

    '<remarks/>
    Public name As String
End Class

'<remarks/>
<System.Xml.Serialization.SoapTypeAttribute("Response", "urn:sample")> _
Public Class Response

    '<remarks/>
    Public returnValue As String
End Class

 上記のソースコードのうち System.Web.Services.Protocols.SoapHttpClientProtocol クラスを継承した HelloServiceService クラスの service メソッドを使用して Web サービスを実行します。.NET 側のオブジェクトのシリアライズ、HTTP POST による SOAP メッセージの送受信、受信データの SOAP メッセージからのデシリアライズは HelloServiceService クラスの Invoke メソッドがすべて実行しています。

(3) クライアント側のアプリケーションを実装する

 フォームデザイナでフォームを作成します。

 ボタンコントロールの Click イベントに以下のコードを書きます。テキストボックスのテキストを Request クラスの name 要素にセットして Web サービスを呼び出し、戻ってきた Response クラスの returnValue 要素をラベルコントロールに表示します。

    Private Sub InvokeButton_Click( _
            ByVal sender As System.Object, ByVal e As System.EventArgs) _
            Handles InvokeButton.Click
        Dim service As HelloServiceService = New HelloServiceService()
        Dim request As Request = New Request()
        request.name = RequestText.Text
        Dim res As Response
        res = service.service(request)
        ResponseLabel.Text = res.returnValue
    End Sub

 実行して、ボタンを押せばテキストに入力した値がラベルに表示されるでしょう。このように、Axis と VB.NET によるリッチクライアントアプリケーションは実に簡単に作成できます。

 実際、通信に使用されている SOAP メッセージは以下のものです。

リクエスト(.NET→J2EE)
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
    xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/"
    xmlns:soapenc="https://schemas.xmlsoap.org/soap/encoding/"
    xmlns:tns="urn:sample"
    xmlns:types="urn:sample/encodedTypes"
    xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="https://www.w3.org/2001/XMLSchema">
  <soap:Body
      soap:encodingStyle="https://schemas.xmlsoap.org/soap/encoding/">
    <tns:service>
      <request href="#id1" />
    </tns:service>
    <tns:Request id="id1" xsi:type="tns:Request">
      <name xsi:type="xsd:string">myName</name>
    </tns:Request>
  </soap:Body>
</soap:Envelope>
レスポンス(J2EE→.NET)
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope
    xmlns:soapenv="https://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsd="https://www.w3.org/2001/XMLSchema"
    xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Body>
    <ns1:serviceResponse
        soapenv:encodingStyle="https://schemas.xmlsoap.org/soap/encoding/"
        xmlns:ns1="urn:sample">
      <serviceReturn xsi:type="ns1:Response">
        <returnValue xsi:type="xsd:string">Hello! myName</returnValue>
      </serviceReturn>
    </ns1:serviceResponse>
  </soapenv:Body>
</soapenv:Envelope>

4. パフォーマンスに関する工夫

 実際のシステム開発プロジェクトに SOAP による通信を適用する場合、まず、気になる点は通信パフォーマンスではないでしょうか? SOAP メッセージは XML なので、ネットワークに流れるデータはテキストデータです。したがってサイズもバイナリデータに比べ大きくなります。今回のアプリケーションのデータサイズはそれほどでもありませんが、複雑なものになると数十 kbyte、または数百 kbyte になってしまうかもしれません。このようなデータサイズではいくらブロードバンドといえどもパフォーマンスは良くないでしょう。

 では、どうすればパフォーマンスが改善されるでしょうか?

 1章で記述したように SOAP 通信にはシリアライズの段階と HTTP 通信の段階があります。この二つの処理の間にデータ圧縮処理を設け、圧縮されたデータにより通信を行います。そうすればネットワークで消費する時間を短くすることができると考えました。

実際どのように実装するか調査したところ、幸いにも .NET Framework にはシリアライズの前後で処理を割り込みさせる仕組みがありました。SoapExtension です。SoapExtension とは System.Web.Services.Protocols の名前空間のクラスで、シリアライズ前後とデシリアライズの前後に処理を組み込むための機能を持っています。

 一方 Axis は少し工夫が必要でした。圧縮されたバイナリデータを受信すると SOAP メッセージとして解釈できず例外をスローしてしまいます。ですので、Axis が Request を受信する前にバイナリデータを解凍するサーブレットを作成ました。同時にそのサーブレットはクライアントに Response を送信する前にテキストデータを圧縮します。

 なお、Axis はバージョン1.0 、Servlet はバージョン 2.3 、.NET Frameworkはバージョン 1.0を使用します。

(1) クライアント側

 クライアント側は SoapExtension を使いました。SoapExtension を使うには、SoapExtension を継承したクラスを定義し、いくつかのメソッドをオーバーライドします。ここでは、継承したクラスとして CompressSoapExtension を定義します。それから、HelloServiceService クラスの service メソッドで、CompressSoapExtension を実行させるために System.Web.Services.Protocols.SoapExtensionAttribute を継承したカスタム属性を定義し、service メソッドに属性を付加します。ここではそのカスタム属性を CompressSoapExtensionAttribute として定義しています。また、圧縮を行うためにCompressメソッドを、解凍を行うためにUncompressメソッドをCompressSoapExtensionクラスに実装します。

 実際のソースコードは以下のようになります。

CompressSoapExtension クラス
Imports System.IO
Imports System.Web.Services.Protocols

'SoapExtensionを継承したクラス
Public Class CompressSoapExtension
    Inherits SoapExtension

    Private oldStream As Stream
    Private newStream As Stream

    Public Overrides Function ChainStream(ByVal stream As Stream) As Stream
        'oldStreamは通信に使うためのストリーム
        oldStream = stream
        'newStreamはクライアント内でデータを処理するためのストリーム
        newStream = New MemoryStream()
        Return newStream
    End Function

    Public Overloads Overrides Function GetInitializer(ByVal methodInfo As LogicalMethodInfo, _
    ByVal attribute As SoapExtensionAttribute) As Object
    End Function

    Public Overloads Overrides Function GetInitializer(ByVal webserviceType As Type) As Object
    End Function

    Public Overrides Sub Initialize(ByVal initializer As Object)
    End Sub

    Public Overrides Sub ProcessMessage(ByVal message As SoapMessage)
        Select Case message.Stage
            Case SoapMessageStage.AfterSerialize
                'シリアライズ後の処理・データの圧縮を行う
                Dim compressedStream As MemoryStream = Compress(newStream)

                '圧縮されたデータをoldStreamに書き込む
                compressedStream.Position = 0
                Dim readByte As Integer = compressedStream.ReadByte()
                While readByte > -1
                    oldStream.WriteByte(readByte)
                    oldStream.Flush()
                    readByte = compressedStream.ReadByte()
                End While
                oldStream.Flush()

            Case SoapMessageStage.BeforeDeserialize
                'デシリアライズの前処理・データの解凍を行う
                Dim uncompressedStream As MemoryStream = Uncompress(oldStream)

                Dim reader As StreamReader = New StreamReader(uncompressedStream)
                Dim rpcMessage As String = reader.ReadToEnd()


                '解凍されたデータをnewStreamに書き込む
                Dim writer As StreamWriter = New StreamWriter(newStream)
                writer.Write(rpcMessage)
                writer.Flush()
                newStream.Position = 0
        End Select

    End Sub

    Protected Function Compress(ByVal outputStream As Stream) As MemoryStream
        '圧縮処理を実装する
    End Function

    Protected Function Uncompress(ByVal inputStream As Stream) As MemoryStream
        '解凍処理を実装する
    End Function
End Class


CompressSoapExtensionAttribute クラス
(CompressSoapExtension を呼び出すためのカスタム属性)
<AttributeUsage(AttributeTargets.Method)> _
Public Class CompressSoapExtensionAttribute
    Inherits SoapExtensionAttribute

    Private pri As Integer

    Public Overrides ReadOnly Property ExtensionType() As Type
        Get
            'SoapExtensionを実装したクラスの型を返す
            Return GetType(CompressSoapExtension)
        End Get
    End Property

    Public Overrides Property Priority() As Integer
        Get
            Return pri
        End Get
        Set(ByVal Value As Integer)
            pri = Value
        End Set
    End Property
End Class

 HelloServiceService クラスの service メソッドに CompressSoapExtensionAttribute の属性を付けます。

HelloServiceServiceクラスのserviceメソッド
    '<remarks/>
    <System.Web.Services.Protocols.SoapRpcMethodAttribute("", RequestNamespace:="urn:sample", ResponseNamespace:="urn:sample"), _
    CompressSoapExtension()> _
    Public Function service(ByVal request As Request) As <System.Xml.Serialization.SoapElementAttribute("Response")> Response
        Dim results() As Object = Me.Invoke("service", New Object() {request})
        Return CType(results(0), Response)
    End Function

 以上が SoapExtension を使って圧縮解凍処理を実装したクライアントのコードです。

(2) サーバ側

 次にサーバ側の説明をします。こちらはクライアントから送信された HttpServletRequest の InputStream を圧縮されたバイナリの状態から解凍してテキストの状態にセットしなおし Axis に渡します。まず、解凍処理を行うためのサーブレットとして Axis の標準の入り口である org.apache.axis.transport.http.AxisServlet を継承したサーブレットを作成しました。 AxisServletWrapper クラスです。以下のコードを見てもらうとわかると思いますが、新しく CompressedServletRequest と CompressedServletResponse をインスタンス化し、uncompress() メソッドや compress() メソッドでデータの加工を行いました。

AxisServletWrapperクラス
package sample.transport;

import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import org.apache.axis.transport.http.AxisServlet;

//通信データの圧縮解凍を行うためのサーブレット
public class AxisServletWrapper extends AxisServlet {
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//解凍処理を行うリクエストクラスをインスタンス化
		CompressedServletRequest req = new CompressedServletRequest(request);
		//圧縮処理を行うレスポンスクラスをインスタンス化
		CompressedServletResponse res = new CompressedServletResponse(response);
		
		//AXISへ処理を渡す
		super.doPost(req, res);
		
		//AXISの処理結果を圧縮してクライアントへ返す
		res.sendResponse();
	}
	
}    

 では、CompressedServletRequest と CompressedServletResponse のつくりです。これらのクラスはそれぞれ javax.servlet.http.HttpServletRequestWrapper クラスと javax.servlet.http.HttpServletResponseWrapper クラスを継承します。
 CompressedServletRequest クラスは AxisServletWrapper が取得した request の InputStream の内容を解凍して、Axis にその内容を渡します。 AxisServlet の中では doPost メソッドで渡された req オブジェクトは、 getInputStream メソッドが呼ばれ ServletInputStream を返します。解凍済みのデータを返すために getInputStream をオーバーライドします。
 CompressedServletResponse クラスは AxisServlet の response 書き込み先として AxisServlet の doPost に渡されます。 AxisServlet の中では doPost メソッドで渡された res オブジェクトは、 getOutputStream メソッドが呼ばれ ServletOutputStream を返します。 AxisServlet はこの ServletOutputStream にレスポンスを書き込むので、レスポンスを圧縮するために getOutputStream メソッドでは作業用の ServletOutputStream を返します。また、クライアントにレスポンスを返すために sendResponse メソッドを準備しました。

CompressedServletRequestクラス
package sample.transport;

import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.ServletInputStream;

//圧縮された通信データを展開してAXISに渡すために使うリクエストクラス
public class CompressedServletRequest extends HttpServletRequestWrapper {

	//解凍された通信データ
	private ByteArrayOutputStream uncompressedStream;

	//コンストラクタ
	public CompressedServletRequest(HttpServletRequest request) throws IOException {
		super(request);
		ServletInputStream compressedStream = request.getInputStream();
		//圧縮された通信データを解凍する
		uncompressedStream = uncompress(compressedStream);
	}
	
	//解凍処理
	private ByteArrayOutputStream uncompress(InputStream inputStream) throws IOException {
		//解凍処理を記述
	}
	
	//AXISから呼ばれるメソッドをオーバーライドする
	//AXISへ解凍された通信データを渡す
	public ServletInputStream getInputStream() {
		return new CompressedServletInputStream(new ByteArrayInputStream(uncompressedStream.toByteArray()));
	}
	
}

CompressedServletResponseクラス
package sample.transport;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.ServletOutputStream;

//AXISのレスポンスを圧縮して、圧縮された通信データを返すためのレスポンスクラス
public class CompressedServletResponse extends HttpServletResponseWrapper {
	
	//クライアントへのレスポンス
	private HttpServletResponse response;
	
	//AXISからのレスポンス
	private CompressedServletOutputStream beforeCompressedStream;
	
	//コンストラクタ
	public CompressedServletResponse(HttpServletResponse response) {
		super(response);
		//クライアントへレスポンスを戻すためにresponseを保持する
		this.response = response;
		
		beforeCompressedStream = new CompressedServletOutputStream();
	}
	
	//AXISから呼ばれるメソッドをオーバーライドする
	//AXISのレスポンス書き込み先ストリームにbeforeCompressedStreamを指定する
	public ServletOutputStream getOutputStream() throws IOException {
		return beforeCompressedStream;
		
	}
	
	//クライアントにレスポンスデータを圧縮して送信する
	public void sendResponse() throws IOException {
		//レスポンスデータを圧縮する
		ByteArrayOutputStream compressedStream = compress(beforeCompressedStream.toByteArrayOutputStream());
		
		//クライアントへ圧縮データを送信する
		ServletOutputStream os = response.getOutputStream();
		compressedStream.writeTo(os);
		compressedStream.flush();
		compressedStream.close();
	}
	
	//圧縮処理
	private ByteArrayOutputStream compress(ByteArrayOutputStream outputStream) throws IOException {
		//圧縮処理を記述
	}

}

 Axis に渡される ServletInputStream として CompressedServletInputStream を、 ServletOutputStream として CompressedServletOutputStream を上記のクラスでは使っていますが、それらのコードは以下のようになります。 

CompressedServletInputStreamクラス
package sample.transport;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import javax.servlet.ServletInputStream;

//CompressedServletRequestのgetInputStreamメソッドの戻り値として使うServletInputStream
public class CompressedServletInputStream extends ServletInputStream {
	
	//解凍済みInputStream
	private ByteArrayInputStream uncompressedInputStream;
	
	//コンストラクタ
	public CompressedServletInputStream(ByteArrayInputStream uncompressedInputStream) {
		super();
		//解凍された通信データをuncompressedInputStreamにセットする
		this.uncompressedInputStream = uncompressedInputStream;
	}
	
	//このクラスがreadされた場合uncompressedInputStreamがreadされる
	public int read() throws IOException {
		return uncompressedInputStream.read();
	}
}

CompressedServletOutputStreamクラス
package sample.transport;

import java.io.IOException;
import java.io.ByteArrayOutputStream;
import javax.servlet.ServletOutputStream;

//AXISからのレスポンスのストリームをこのクラスに一時的に格納する
public class CompressedServletOutputStream extends ServletOutputStream {
	
	//圧縮前のOutputStrem
	private ByteArrayOutputStream beforeCompressStream;
	
	//コンストラクタ
	public CompressedServletOutputStream() {
		super();
		this.beforeCompressStream = new ByteArrayOutputStream();
	}
	
	//このCompressedServletOutputStreamへの書き込みをbeforeCompressStreamへ書き込む
	public void write(int arg) throws IOException {
		beforeCompressStream.write(arg);
	}
	
	//ByteArrayOutputStream型でCompressedServletOutputStreamの内容を返す
	public ByteArrayOutputStream toByteArrayOutputStream() {
		return beforeCompressStream;
	}
}

 

 以上がサーブレットを使った圧縮解凍処理を実行するためのコードです。上記の AxisServletWrapper を AxisServlet の代わりに web.xml に登録すると圧縮解凍処理が実装された SOAP サーバとして動作します。

 実際に、AxisServlet から直接 Axis を呼ぶ場合と、圧縮解凍機能を持ったサーブレットを通して Axis を呼ぶ場合とで通信時間の測定をしましたが、確かに効果はありました。ネットワークの環境によって異なってくるとは思いますが、データが大きければ大きいほどパフォーマンスの向上は大きくなるでしょう。また、圧縮方法を選択して細かくチューニングすることも可能だと思います。データサイズが小さいと圧縮通信を行うと逆にパフォーマンスが落ちる場合もありますが、そのような場合は圧縮をさせない機能を実装することも可能だと思います。

 この仕組みで圧縮解凍を行っている部分を暗号化復号化処理に書き換えれば暗号化通信にも使えるのではないかと思います。


5. 最後に

 以上、駆け足で .NET と J2EE を Web サービスにより連携させる方法を記述しました。Axis が動作している J2EEサーバ と .NET Framwork によるリッチクライアントの連携の仕組みは作成するのが実に簡単なのはわかっていただけたかと思います。組織内部で使用する Web サービスは実際に用いられることが多くなってきているようです。J2EE サーバと .NET クライアントによるシステム構成は、リッチクライアントなシステムを構築する上で、一つの候補になるのではないでしょうか?

 現在 Web サービスは大変ホットな技術要素の一つだと思います。日々いろいろな動きがありキャッチアップしていくべきこともたくさんありますが、今後はさらに一般的に使用されるようになるでしょう。今回ご紹介した内容が少しでもみなさんのお役に立てれば幸いです。 また、ご意見、ご指摘等もよろしくお願いいたします。

《参考文献》


© 2003 OGIS-RI Co., Ltd.
HOME HOME TOP オブジェクトの広場 TOP