ものがたり(旧)

atsushieno.hatenablog.com に続く

.NET 3.5のWCF Routingを実装しよう

先週末にふと思い立ってやっつけでWCF 4.0 Routingを実装したので、ざっくりとした話を書こうと思う。ちなみにまだ完成してはいない。configurationまわりは途中までしか作っていない。内部動作の説明は例によってMonoのものであって.NETのものは知らない。

WCF Routingは、クライアントが直接サービスを呼び出す方式ではなく、クライアントが"router"となるサービスにリクエストを発行すると、routerがそのときの気分やら設定やらによって、対応するサービスにリクエストを転送し、その結果をクライアントに返す、というものだ。そのルーティングの規則は、MessageFilterと、それに対応するServiceEndpointによって決まる*1

Usage

ルーティングはRoutingServiceというクラスが実装している。ルーティングのServiceContractとなるインターフェースは、セッションの方式に合わせて4種類(ISimplexDatagramRouter, ISimplexSessionRouter, IDuplexSessionRouter, IRequestReplyRouter)あって、RoutingServiceはこれを全て実装している。

これらのインターフェースのOperationContractとなるメソッドは、いずれもMessage型の引数をとるもので、具体的なクライアント・サーバ間のメッセージのコントラクトは関知しない。

ルーティング テーブルの設定は、RoutingConfigurationというクラスで定義されていて、このクラスのインスタンスを、RoutingBehaviorというIServiceBehaviorの実装クラスがメンバとして保持している。RoutingConfigurationには、前述のMessageFilterとServiceEndpointのテーブルなどが含まれる。

WCF Routingを使う人は、RoutingServiceのホストを用意して(ServiceHostやASP.NETなど)、そのbehaviorとしてRoutingBehaviorを追加する(そこには自分でMessageFilterとServiceEndpointの設定を加えたRoutingConfigurationを渡してやる)、という手順で、ルーティング サービスを公開できる。コードで書くとこんな感じだ:


var host = new ServiceHost (typeof (RoutingService));
host.AddServiceEndpoint (
typeof (IRequestReplyRouter),
new BasicHttpBinding (),
new Uri ("http://localhost:8081/router"));
var config = new RoutingConfiguration ();
var clientEndpoint = new ServiceEndpoint (
ContractDescription.GetContract (typeof (IRequestReplyRouter)),
new BasicHttpBinding (),
new EndpointAddress ("http://localhost:8080/service"));
var list = new List ();
list.Add (clientEndpoint);
config.FilterTable.Add (new MatchAllMessageFilter (), list);
host.Description.Behaviors.Add (new RoutingBehavior (config));

Internals

RoutingBehaviorがServiceHostにApplyDispatchBehavior()によって適用されると、まずRoutingExtensionというクラスがServiceHostBaseのIExtensionとしてアタッチされる。そして、このRoutingExtensionに、RoutingBehaviorに割り当てられたRoutingConfigurationがApplyConfiguration()というメソッドを通じて設定される。

そして、もしRoutingConfigurationにおいてSOAPエンベロープのすげ替えがSoapProcessingEnabledプロパティによって有効になっていたら(これがデフォルト)、RoutingConfigurationに含まれる全てのServiceEndpointに、IEndpointBehaviorの実装であるSoapProcessingBehaviorが追加される。このbehaviorは、後でルーティングされるMessageのMessageVersionを調整する(クライアントからrouterへの送信に使われたMessageVersionから、実際のサービスで要求されるMessageVersionにすげ替えたり、その逆を復路で行ったりする)のに用いられる。ユーザがコードで使用する必要は無い。

RoutingConfigurationが適用されると、このRoutingExtensionは、内部的にRoutingServiceのインスタンス化を制御するIInstanceProviderをデフォルトのものからすげ替えて、サービスメソッド呼び出し時に生成される新しいRoutingServiceインスタンスに、内部的にRoutingConfigurationが渡されるようにする。

RoutingServiceが生成され、その(セッション方式によって異なるServiceContractインターフェースとなっている)サービスメソッドが呼び出されるたびに、ルーティングのクライアントがMessageFilterTable`2に設定されたServiceEndpointから生成され、実際のサービスに向けてリクエストが発行される。この時、先に説明したSoapProcessingBehaviorが、Messageに手を加えて、SOAPヘッダの内容などを置き換える(たとえば、クライアントから送られてくるToヘッダの宛先はrouter自身だが、これはサービスのEndpointAddressに置き換える必要がある)。これはWCFにおけるIClientMessageInspectorの仕組みを活用して、このインターフェースの実装クラスとして行われる。

ちなみにSoapProcessingBehaviorのインターフェースであるIEndpointBehaviorというのは厄介なもので、ChannelFactory<T>を使用しないと、ApplyClientBehavior()を適用される場面が無い。これが呼び出されないと、IClientMessageInspectorを設定する機会が無いので、転送に必要なSOAPメッセージの改変が行われなくなってしまう。最初は、Bindingから自前でIChannelFactoryを生成した方が、チャネルの管理は行いやすいので、そうしていたのだが、それでは上手くいかないことに気付いて少し悩んだ。

Remaining Topics

ルーティングは、Request-Reply方式のメッセージだけでなく、simplex, duplexのメッセージにも適用できる。そして、それらについては、複数の転送先を指示することもできる(request-replyの場合はreplyの処理がある関係で1つのみである)。これについては詳しくは書かない。

configurationについても、まだ実装が完了していないので割愛する。

最後にひとつだけおいしい(?)話を書いておこう。.NET 4.0のWCF Routingは.NET 4.0でしか使えないが、MonoのWCF Routingは.NET 3.5があれば使うことが出来る。mcs/class/System.ServiceModel.Routingディレクトリでmakeすれば、2.0ランタイム用のSystem.ServiceModel.Routing.dllの出来上がりだ。まあ、ちゃんと動くかどうかは分からないけど。

というわけで、タイトルは釣りだけど単なる釣りではないのでした。

*1:MessageFilterTable<IEnumerable<ServiceEndpoint>>というややこしい型の変数に格納される