ファイヤープロジェクト
SOAPHeaderとクライントサイドのハンドラチェイン(AXIS 1.3)
2005-11-06T14:30+09:00   matsu
AXISにおけるSOAPHeaderの扱いを調査,確認してみた.さらに,SOAPHeaderを送信するにあたって,前頁のハンドラチェイン設定をクライントサイドにも適用してみた.
SOAP電文は,0個以上のSOAPHeaderと一つのSOAPBodyから構成される. 今まではSOAPBodyだけからなるSOAP電文を処理するサンプルを作成してきた. Webサービスの処理の対象となるデータの主役はSOAPBodyに格納される. SOAPHeaderには,それに付加的な情報あるいは処理において必須ではない情報,ターゲットの前処理に必要な情報などを格納する. さて,今回はクライアント側でSOAP要求電文にSOAPHeaderを付加し,サーバ側でそれを処理するサンプルを作成した. このサンプルの構成は,基本的に従来のクライアントとサーバを作成し,さらにSOAPHeaderを付加するハンドラと処理するハンドラを作成し,クライアント側,サーバ側でそれぞれチェインを定義してこれらのプログラムを連係させるという構成ととる. このような構成をとることによって,SOAPBody処理に関するプログラムとSOAPHeaderを処理するプログラムを分離,モジュール化し,再構成,拡張を行いやすくできる. なお,AXISにおいては,もちろんピボットハンドラにおいてSOAPHeaderを処理することも可能であり(※),アプリケーションによってはそのように構成するほうが妥当な場合もあるかもしれない.
※ MessageContextオブジェクトはどこからでも取得可能であり,SOAPHeaderを含むSOAP電文はその中にある.
サーバ側で稼働するSOAPHeader処理クラスは,FirstHeaderHandlerである. サーバ側のdeployment.wsddを以下に示す.
まず,ヘッダを処理する二つのハンドラの定義がある.
  <handler name="firstHeaderHandler1"
           type="java:org.fireproject.axissample.FirstHeaderHandler">
    <parameter name="required" value="true"/>
  </handler>

  <handler name="firstHeaderHandler2"
           type="java:org.fireproject.axissample.FirstHeaderHandler">
    <parameter name="required" value="false"/>
  </handler>
いずれもFirstHeaderHandlerクラスにより処理を行うが,パラメータrequiredの値が異なる. SOAPHeaderには,次節に記述するように,そのヘッダが必ず処理されるべきか否かを指定する属性mustUnderstandがある. これがあるのに処理されないヘッダがあると,SOAPエンジンであるAXISによってSOAPFaultが返される. パラメータrequiredはそれと逆のイメージで実装した. すなわち,処理対象としているSOAPHeaderがサーバ側で必ずなくては困るヘッダであるかどうかを示す. この値がtrueであるのに,要求電文に対象SOAPHeaderが無ければエラーを返す. したがって,そのしたで定義されている二つのサービスでは,同じピボットハンドラクラスであるにも関わらず,SOAPHeaderであるfirstHeader(FirstHeaderHandlerが処理するSOAPHeader)が必須か否かが異なる.
  <service name="firstHeaderRequired" provider="java:MSG">
    <parameter name="className" value="org.fireproject.axissample.HeaderSampleService"/>
    <requestFlow>
      <handler type="firstHeaderHandler1"/>
    </requestFlow>
  </service>

  <service name="firstHeaderOptional" provider="java:MSG">
    <parameter name="className" value="org.fireproject.axissample.HeaderSampleService"/>
    <requestFlow>
      <handler type="firstHeaderHandler2"/>
    </requestFlow>
  </service>
さらに最後のサービスは,ヘッダを処理するハンドラがないのでfirstHeaderがその有無に関わらず処理されない.
  <service name="noFirstHeader" provider="java:MSG">
    <parameter name="className" value="org.fireproject.axissample.HeaderSampleService"/>
  </service>
サーバ側で,ヘッダを解析して処理するクラスFirstHeaderHandlerを以下に示す.
例によってMessageContextを使用して,SOAP電文を取得,さらにSOAPHeaderを取得.
// 要求電文取得
Message request = msgContext.getRequestMessage();
// SOAPHeader取得
SOAPEnvelope envelope = request.getSOAPEnvelope();
SOAPHeaderElement header = envelope.getHeaderByName("http://www.fireproject.jp", HEADER_NAME);
次に,取得したヘッダに対して,処理済みであることを設定する.
// ヘッダ処理済みフラグを設定.
header.setProcessed(true);
文字通りこれを設定することによって,ヘッダが処理されたかどうかがAXISや他のハンドラが知ることができる. AXISによる属性mustUnderstandのあるヘッダが処理されたかどうかの判定にも影響があるので,忘れずに設定する. サンプルでは,さらに該当SOAPHeaderと結びつくアクター(=誰が処理したか?)を設定している.
// アクターを設定.
header.setActor(this.getClass().getName());
この辺りの処理を記述したら,あとはメイン処理を記述していく. ここでは,後の確認のために,要素valueの値が"error"ならAXISFaultを投げるようにしておいた. 最後にピボットハンドラであるHeaderSampleServiceを示す.
今回とくに新しいポイントはないが,とにかく要求電文で要素valueの内容であるテキストがerrorならAXISFaultを投げるようにしておいた. それ以外の場合は,要求電文の要素valueの値を返す.
前頁でハンドラチェインの概念と設定方法を記述した. 本頁のサーバ側でもハンドラチェインを設定している. AXISは,あくまでSOAPエンジンであり,WEBサービスサーバではない. サーバ側では,SOAPエンジンとしてトランスポートにHTTP通信用ハンドラを設定して,WEBサービスサーバを実現している. 下図は,前節のハンドラチェインの概念図をトランスポートから捉え直したものである(※).
すなわちハンドラチェインの概念は,クライアント側でも適用可能である. 但し,トランスポート部分の位置付けが異なるので注意. 上のサーバ側のハンドラチェイン概念図に対して,クライアント側のハンドラチェインの概念図は以下のようになる.
このように,サーバ側ではトランスポート部分でSOAP電文処理がキックされてピボットハンドラをぐるっと回ってかえってくるのに対し,クライアント側ではクライアントプログラムの上位部分がSOAP電文処理をキックして,トランスポートをぐるっと回ってかえってくる. より厳密に言うと,サーバ側では,チェインの端がトランスポートでピボットハンドラが各種アプリケーション用のハンドラ,クライアント側では各種アプリケーション用のプログラムがチェインの端(但しチェインの外側と言えるかもしれない)で,ピボットハンドラがトランスポートとなる. クライアント側では,クライアントプログラム実行ディレクトリにclient-config.wsddがあれば,それをハンドラチェイン定義として利用する. なければトランスポートにHTTP通信用ハンドラを使用するというデフォルトの設定で起動する. 以下にclient-config.wsddを示す.
まず最初にトランスポートハンドラとして使用するHTTPSenderを要素handlerにて定義する. HTTPSenderは,AXISにて提供されちれているトランスポートハンドラである. 今までのサンプルも,設定はしなかったのでデフォルトでこれが使用されていた. 次に,ヘッダ付加用のハンドラをハンドラとして定義する.
  <handler name="firstHeaderAdder" type="java:org.fireproject.axissample.SimpleHeaderAdder">
    <parameter name="namespace" value="http://www.fireproject.jp"/>
    <parameter name="headerName" value="firstHeader"/>
    <parameter name="value" value="firstHeaderValue"/>
    <!--parameter name="value" value="error"/-->
    <parameter name="mustUnderstand" value="true"/>
  </handler>
これは今回作成したサンプルである. 要素parameterにてSOAPHeaderに記述する内容を大体設定できるようにしておいた. 最後にトランスポートチェインの定義.
  <transport name="http" pivot="HTTPSender">
    <requestFlow>
      <handler name="requestFlowEntranceLog" type="java:org.apache.axis.handlers.LogMessage">
        <parameter name="message" value="requestFlowEntrance, here"/>
      </handler>
      <handler type="firstHeaderAdder"/>
    </requestFlow>
    <responseFlow>
      <handler name="responseFlowExitLog" type="java:org.apache.axis.handlers.LogMessage">
        <parameter name="message" value="responseFlowExit, here"/>
      </handler>
    </responseFlow>
  </transport>
このように要素transportにて定義する. 要素trasnportの属性は以下.
name
該当トランスポートチェインが担当するトランスポート層プロトコル
pivot
サーバ側とはことなり,クライアント側ではピボットハンドラがトランスポートチェイン内にある. そのハンドラをこのpivot属性にて指定する. つまりトランスポートハンドラを記述する.
このトランスポートチェインのrequestFlowにて,ヘッダ付加を行うハンドラfirstHeaderADderを指定している. これによって,クライアントは従来通りSOAP要求電文を作成し,その生成されたSOAP要求電文がrequestFlowを通る際に,firstHeaderAddrがSOAPHeaderを付加する.
※ グローバルチェイン(globalChain)については,別途調査する.
ヘッダを付加するハンドラは,通常のハンドラとほとんど同じ容量で作成できる. 以下にfirstHeaderAddrの処理クラスSimpleHeaderAdderを示す.
SOAP要求電文はクライアントによって既に作成済みなので,まずそれを取得する.
// 要求電文取得
Message request = msgContext.getRequestMessage();
SOAPEnvelope envelope = request.getSOAPEnvelope();
あとはSOAPHeaderを作成して,パラメータをセットして,要求電文にセットするだけである.
SOAPHeaderElement header = new SOAPHeaderElement(nameSpace, headerName);
header.addTextNode(value);

// SOAPHeader付加
envelope.addHeader(header);

header.setMustUnderstand(mustUnderstand());
今回のサンプルでは,client-config.wsddにて各種パラメータを指定できるようにした.
今回のサンプルでは,SOAPHeader処理に関する構成を設定によってある程度変更できるようにした. 以降にいくつかのパターンで試してみる. サンプル実行にあたっては,
src/WEB-INF/client-config.wsdd
をクライアントプログラムを実行するカレントディレクトリにコピーする. ではまず,ヘッダが必須となっているサービスfirstHeaderRequiredに対して,ヘッダつきで要求. client-config.wsddでヘッダ付加ハンドラを以下のように設定.
  <handler name="firstHeaderAdder" type="java:org.fireproject.axissample.SimpleHeaderAdder">
    <parameter name="namespace" value="http://www.fireproject.jp"/>
    <parameter name="headerName" value="firstHeader"/>
    <parameter name="value" value="firstHeaderValue"/>
    <parameter name="mustUnderstand" value="true"/>
  </handler>
要求電文は以下のようになる.
要素soapBodyの前に要素soapHeaderが付加される. 次に,ヘッダの名前空間を,サーバ側で想定しているものとは異なるものに変更.
    <parameter name="namespace" value="http://invalid.ns"/>
そして実行.
java -jar axissample.jar http://localhost:8180/fireproject/services/firstHeaderRequired hoge
第二引数はSOAPBodyに設定される値で,errorとすると,ピボットハンドラにてSOAPFaultが生成される. それ以外の場合,ピボットホンドラまで到達できれば,指定した値がそのまま回答される. サービスfirstHeaderRequiredではfirstHeaderは必須だが,名前空間が一致するfirstHeaderがないので,ヘッダ処理ハンドラにてエラーが返される.
同じ要求電文でも,サービスfirstheaderOptionalは,firstHeaderが無くても正常に返されるように思えるが,今度は逆にfirstHeaderにmustUnderstandが設定されているにも関わらず処理されないことをAXISが検知してエラーを返す.
ではclient-config.wsddでmustUnderstand属性をfalseとする.
    <parameter name="namespace" value="http://invalid.ns"/>
...省略...
    <parameter name="mustUnderstand" value="false"/>
  </handler>
こうすると,サービスfirstheaderOptionalは正常に回答される.
ただしヘッダは処理されない. もう一度,client-config.wsddにて正しい名前空間にてヘッダを設定する. ただし今度はヘッダの要素valueの値をerrorとする.
  <handler name="firstHeaderAdder" type="java:org.fireproject.axissample.SimpleHeaderAdder">
    <parameter name="namespace" value="http://www.fireproject.jp"/>
    <parameter name="headerName" value="firstHeader"/>
    <parameter name="value" value="error"/>
    <parameter name="mustUnderstand" value="false"/>
  </handler>
要素valueの値がerrorなので,ヘッダ処理にてエラーが返される.
最後に同じclient-config.wsddにてサービスnoFirstHeaderに投げてみる.
今度は,サーバ側で要求電文がヘッダ処理ハンドラを通過しないので,ヘッダは処理されないのでエラーとならず,正常に回答される.
matsu(C)
Since 2002
Mail to matsu