特集 
次世代XML Webサービスを試す Part 3  
SOAPメッセージのルーティング(後編)
4.WSEによるルータの実装 
インフォテリア株式会社 
吉松 史彰 
2003/02/27 
 | 
 
            
   
 | 
      
サーバ側での経路指定
 前半では、XML Webサービスの呼び出しの際に、クライアント側で設定したルート情報によりSOAPメッセージのルーティングを行う実装を紹介した。しかしこの実装では、クライアント側でサーバ側の経路情報を把握しておかなければならず、ファイアウォールやロード・バランサのようなルータは実現できない。
 WSEでは、サーバ側で経路を決めて、WS-Routingのヘッダを設定することもできるようになっている。というより、現実的にはこちらの方がよく使われることになるだろう。WSEでは、サーバ側で経路を決定する方法が2種類ある。1つはルータを自分で実装する方法で、もう1つはWS-Referral仕様に基づくリファラル・キャッシュを参照する方法だ。
ルータの実装
 WSEでは、自分でルータを実装してルーティングを行うことを「Content-based Routing」と呼んでいる。メッセージの内容に応じてルートを決定するからだ。これによって、ファイアウォールやロード・バランサのようなルーティングを行うことができる。ルータを自分で実装するには、次の手順を踏めばよい。ここでは以下の図のようなルートで呼び出されるXML Webサービスと、そのクライアントを作成してみる。
 
  | 
 
| 自分でルータを実装して行うルーティング | 
 
|  ルータは、メッセージの内容に応じてルートを決定することができる。 | 
 Microsoft.Web.Services.Routing.RoutingHandlerクラスを継承する独自クラスを作成して、以前の連載ですでに作成済みのWebサービス・プロジェクト「WSERouting」に追加する。ここではクラスの名前を「CapriciousRouter」とした。
public class CapriciousRouter : 
    Microsoft.Web.Services.Routing.RoutingHandler { 
  …… 
} 
 | 
 
 
 | 
 
|  Microsoft.Web.Services.Routing.RoutingHandlerクラスを継承した独自のCapriciousRouterクラス | 
 RoutingHandlerクラスのProcessRequestMessageメソッドをオーバーライドする。
 次にロード・バランシングやセキュリティ機構などの独自のルーティング・ロジックを実装する。ここではまずメッセージの内容を調べて、条件に一致するメッセージであれば3つのルータからランダムに1つ選び、フォワード・パスに追加している。3つのルータはそれぞれ単に最終的な受信者にメッセージを転送する。
protected override void ProcessRequestMessage( 
                    SoapEnvelope message, Path outgoingPath) { 
  XmlNamespaceManager mgr 
                    = new XmlNamespaceManager(new NameTable()); 
  mgr.AddNamespace("ns1", "http://tempuri.org/"); 
 
  if (message.Body.SelectSingleNode("//ns1:input", mgr) 
                          .FirstChild.Value.Equals("hello")) { 
    Random d = new Random(); 
    int i = d.Next(Int32.MaxValue); 
    Uri next = new Uri(String.Format( 
                    "http://localhost/WSERouting/{0}.ashx", 
                    Convert.ToChar((i % 3) + 'a'))); 
    outgoingPath.Fwd.Insert(0, new Via(next)); 
  } else 
    base.ProcessRequestMessage(message, outgoingPath); 
} 
 | 
 
 
 | 
 
|  オーバーライドしたProcessRequestMessageメソッド | 
| このメソッドで独自のルーティング・ロジックを実装できる。ここでは例として、ランダムな経路を1つ選択するルーティングを実装した。 | 
 Web.configファイルを編集し、次のエントリをsystem.web要素の中のhttpHandlers要素に追加する。
 これによって、「router.ashx」へのアクセスに対して手順
で作成したクラスが呼び出されることになる。このとき、すでに記述してある「*.ashx」のエントリより上に記述しなければならないことに注意してほしい拡張子が同じであるため、*.ashxが先に見付かってしまうと、手順
で記述したコードは実行されなくなってしまう。
<add type="WSERouting.CapriciousRouter, WSERouting" 
                              path="router.ashx" verb="*" /> 
 | 
 
 
 | 
 
|  Web.configファイルでのHTTPハンドラの追加 | 
これにより、「router.ashx」へのアクセスに対して、 で作成したロジックが呼び出されるようになる。 | 
 Service1.asmx.csのコードを書き換えて、どのルータが処理したか分かるようにする。
 次のコードで、Timestamp要素のReceived子要素から処理したノードを取得できる。
[WebMethod] 
public string Echo(string input) { 
  SoapContext ctx = HttpSoapContext.RequestContext; 
  return input + " Processed by " 
               + ctx.Timestamp.Receivers[1].Actor.AbsoluteUri; 
} 
 | 
 
 
 | 
 
|  Webサービス・メソッドの修正 | 
| Webサービスが返す文字列に、ルーティングを処理したノードのURLを含める。 | 
 これでサーバ側ができたので、クライアント側のコードも変更する。クライアント側では、取りあえず自分から直接送信する相手として、最終的な受信者への到達方法を知っているルータを指定すればよいことになる。
static void Main(string[] args) { 
  localhost.Service1 svc = new localhost.Service1(); 
  SoapContext ctx = svc.RequestSoapContext; 
  ctx.Path.Fwd.Add(new 
      Via(new Uri("http://localhost/WSERouting/router.ashx"))); 
  Console.WriteLine(svc.Echo("hello")); 
} 
 | 
 
 
 | 
 
|  修正したWebサービス・クライアント | 
| クライアントでは、上記で作成したルータを最初の経路として設定し、Webサービスの呼び出しを行う。 | 
 このクライアント・アプリケーションを実行すると、次のような実行結果となる。
 
  | 
 
| Webサービス・クライアントの実行例 | 
 
|  この場合、a.ashxを経由してWebサービスが呼び出されていることが分かる。 | 
 この場合、まず次のようなメッセージがrouter.ashxに送信される。to要素にはService1.asmxが設定されている。またfwd/via要素にはrouter.ashxしか設定されていないことに注意してほしい。
<soap:Envelope 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
  <soap:Header> 
    <wsrp:path 
        soap:actor="http://schemas.xmlsoap.org/soap/actor/next" 
        soap:mustUnderstand="1" 
        xmlns:wsrp="http://schemas.xmlsoap.org/rp"> 
      <wsrp:action>http://tempuri.org/Echo</wsrp:action> 
      <wsrp:to>http://localhost/WSERouting/Service1.asmx</wsrp:to> 
      <wsrp:fwd> 
        <wsrp:via> 
          http://localhost/WSERouting/router.ashx 
        </wsrp:via> 
      </wsrp:fwd> 
      <wsrp:id>uuid:57839732-131e-4eb2-a7cb-bd36328895cc</wsrp:id> 
    </wsrp:path> 
    <wsu:Timestamp 
        xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"> 
      <wsu:Created>2003-02-01T11:02:32Z</wsu:Created> 
      <wsu:Expires>2003-02-01T11:07:32Z</wsu:Expires> 
    </wsu:Timestamp> 
  </soap:Header> 
  <soap:Body> 
    <Echo xmlns="http://tempuri.org/"> 
      <input>hello</input> 
    </Echo> 
  </soap:Body> 
</soap:Envelope> 
 | 
 
 
 | 
 
|  クライアントからrouter.ashxに送信されるSOAPメッセージ | 
| 最終的な受信者を示すto要素にService1.asmxが設定される。経路上の中継者を示すfwd/via要素にはrouter.ashxしか設定されていない。 | 
 router.ashx(実体はCapriciousRouter)は上記のメッセージを受け取って中身を確認し、次のようなメッセージを生成して選択されたルータに転送する。fwd/via要素に新しくa.ashxが付け加わっている。これはrouter.ashxが選択した次の経路を意味する。
<soap:Envelope 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
  <soap:Header> 
    <wsu:Timestamp 
        xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"> 
      <wsu:Created>2003-02-01T11:02:32Z</wsu:Created> 
      <wsu:Expires>2003-02-01T11:07:32Z</wsu:Expires> 
      <wsu:Received Actor="http://localhost/WSERouting/router.ashx" 
          Delay="691"> 
        2003-02-01T11:02:42Z 
      </wsu:Received> 
    </wsu:Timestamp> 
    <wsrp:path 
        soap:actor="http://schemas.xmlsoap.org/soap/actor/next" 
        soap:mustUnderstand="1" 
        xmlns:wsrp="http://schemas.xmlsoap.org/rp"> 
      <wsrp:action>http://tempuri.org/Echo</wsrp:action> 
      <wsrp:to>http://localhost/WSERouting/Service1.asmx</wsrp:to> 
      <wsrp:fwd> 
        <wsrp:via>http://localhost/WSERouting/a.ashx</wsrp:via> 
      </wsrp:fwd> 
      <wsrp:id>uuid:57839732-131e-4eb2-a7cb-bd36328895cc</wsrp:id> 
    </wsrp:path> 
  </soap:Header> 
  <soap:Body> 
    <Echo xmlns="http://tempuri.org/"> 
      <input>hello</input> 
    </Echo> 
  </soap:Body> 
</soap:Envelope> 
 | 
 
 
 | 
 
|  router.ashxが選択されたルータに転送するSOAPメッセージ | 
| fwd/via要素に新しくa.ashxが付け加わっている。 | 
 a.ashxはメッセージを受け取ると、次のメッセージをService1.asmxに送信する。
<soap:Envelope 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
  <soap:Header> 
    <wsu:Timestamp 
        xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"> 
      <wsu:Created>2003-02-01T11:02:32Z</wsu:Created> 
      <wsu:Expires>2003-02-01T11:07:32Z</wsu:Expires> 
      <wsu:Received Actor="http://localhost/WSERouting/router.ashx" 
          Delay="691"> 
        2003-02-01T11:02:42Z 
      </wsu:Received> 
      <wsu:Received Actor="http://localhost/WSERouting/a.ashx" 
          Delay="781"> 
        2003-02-01T11:02:42Z 
      </wsu:Received> 
    </wsu:Timestamp> 
    <wsrp:path 
        soap:actor="http://schemas.xmlsoap.org/soap/actor/next" 
        soap:mustUnderstand="1" 
        xmlns:wsrp="http://schemas.xmlsoap.org/rp"> 
      <wsrp:action>http://tempuri.org/Echo</wsrp:action> 
      <wsrp:to>http://localhost/WSERouting/Service1.asmx</wsrp:to> 
      <wsrp:id>uuid:57839732-131e-4eb2-a7cb-bd36328895cc</wsrp:id> 
    </wsrp:path> 
  </soap:Header> 
  <soap:Body> 
    <Echo xmlns="http://tempuri.org/"> 
      <input>hello</input> 
    </Echo> 
  </soap:Body> 
</soap:Envelope> 
 | 
 
 
 | 
 
|  ルータa.ashxがWebサービスService1.asmxに送信するSOAPメッセージ | 
 Service1.asmxは自分がto要素に設定されていることを確認して、受け取ったメッセージのBody部分だけ処理し、結果を送信元(ここではa.ashx)に返す。a.ashx(RoutingHandler)は受け取った結果をそのままrouter.ashxに返し、router.ashx(CapliciousRouter)はクライアントに結果を返信する。
<soap:Envelope 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
  <soap:Header> 
    <wsu:Timestamp 
        xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"> 
      <wsu:Created>2003-02-01T11:02:43Z</wsu:Created> 
      <wsu:Expires>2003-02-01T11:07:43Z</wsu:Expires> 
      <wsu:Received Actor="http://localhost/WSERouting/a.ashx" 
          Delay="123"> 
        2003-02-01T11:02:44Z 
      </wsu:Received> 
      <wsu:Received Actor="http://localhost/WSERouting/router.ashx" 
          Delay="143"> 
        2003-02-01T11:02:44Z 
      </wsu:Received> 
    </wsu:Timestamp> 
  </soap:Header> 
  <soap:Body> 
    <EchoResponse xmlns="http://tempuri.org/"> 
      <EchoResult> 
        hello Processed by http://localhost/WSERouting/a.ashx 
      </EchoResult> 
    </EchoResponse> 
  </soap:Body> 
</soap:Envelope> 
 | 
 
 
 | 
 
|  最終的にクライアントに返されるSOAPメッセージ | 
 このようにすれば、WSEを利用してサーバ側でメッセージの内容やあて先に応じた経路を設定できるようになる。