ものがたり(旧)

atsushieno.hatenablog.com に続く

サービス側のリクエスト処理

ここからはサービス側の処理フローの説明になる。

WebHttpDispatchOperationSelector

サービスがメッセージを受け取るのは、TransportBindingElementから生成されたIChannelListenerから生成された IReplyChannelやIInputChannelを通じてのことである。WebHttpBindingの場合、 TransportBindingElementはHttpTransportBindingElementのみであり、その内部では HttpTransportBindingElementから生成されたIReplyChannel(面倒なので今後HttpReplyChannelと仮称しよう)か生成されたMessageであることが求められる。正確には、生成されたMessageに HttpRequestMessagePropertyやHttpResponseMessagePropertyが求められる。

さて、HttpReplyChannelから生成されてきたMessageは、ChannelDispatcherと EndpointDispatcherを経由してendpointに渡される。そして、そのServiceEndpointのContractに含まれるどのOperationの呼び出しと見なされるべきか、DispatchRuntimeのOperationSelectorによって判断されなければならない。それがIDispatchOperationSelectorである。WebHttpDispatchOperationSelectorはこれを実装するものだ。

ちなみに、DispatchRuntime.OperationSelectorは、WebHttpBehaviorがApplyDispatchBehavior()を呼び出された際に設定される。

UriTemplateTable, UriTemplateMatch

さて、WebHttpDispatchOperationSelectorは、Messageの情報から、endpointにあるoperationを選択しなければならない。その基準として利用されるのが、再びUriTemplateである。

繰り返しになるが、UriTemplateは、パラメータのテンプレートを独自のエスケープ構文で記述したURIテンプレート文字列から生成される:

  • /foo?p1={foo}&p2={bar}
  • /foo/{bar}/{baz}

そして実際にMessageに含まれるリクエストのパス/クエリは、以下のようなものになる:

  • /foo?p1=hoge&p2=fuga
  • /foo/2008/0101

このような実際の呼び出しURLが、あるURIテンプレート文字列にマッチするか判断したり、マッチした場合にパラメータの値を取り出すために利用されるのが、UriTemplateとUriTemplateMatchである。

さて、1つのendpointには1つのserviceと複数のoperationが含まれているので、あるMessageがどのoperationに向けられたものであるかを判断しなければならない。BasicHttpBindingなどの場合は、SOAP Actionをもとに判断していたが、WebHttpBindingの場合は、リクエストURLから判断されることになる。このために用いられるのが UriTemplateTableである。

UriTemplateTableには、複数のUriTemplateをもたせておいて、あるリクエストが来たら、そのURIがマッチするUriTemplateを適宜選択することができる(どれにも該当しなければエラーとなる)。

WebHttpDispatchOperationSelectorは、Message.Headers.ToのUriをもとに、このUriTemplateTableを使用して、マッチするUriTemplateをもつoperationを選択しているのである。

ちなみに、WebGetAttributeあるいはWebInvokeAttributeは、WebHttpDispatchOperationが ServiceEndpointから生成される時、そのContractに含まれるOperationDescriptionのそれぞれについて、 Behaviorとして追加されている必要がある(前述の通り、通常はContractDescription.GetContract()で追加されるはずである)。

ちなみに、UriTemplateは、ベースアドレスとなるUriに、特定の文字列パラメータをバインドして、Uriを生成することもできる。これはクライアント側でHTTP GETリクエストの送り先を決定するために用いられる。

IDispatchMessageFormatterとQueryStringConverter

さて、対象となるOperationが選択できたら、そのサービスメソッドをCLIランタイムの文脈で呼び出せるように、Messageをランタイムオブジェクトのパラメータ群に変換しなければならない。これを行うのがIDispatchMessageFormatter.DeserializerRequest()である。

Messageをランタイムオブジェクトに変換するには2つのステップが必要になる。

  • UriTemplate(とUriTemplateMatch)を用いて、リクエスURIからパラメータ値を文字列で取得する
  • それぞれのパラメータについて、文字列からランタイムオブジェクトを生成する

文字列パラメータからランタイムオブジェクトを生成する時に使用されるのが、再び登場するQueryStringConverterである。リクエストパラメータ文字列をランタイムオブジェクトに変換するにはこれのConvertStringToValue()を使用する。

WebHttpBehavior.GetQueryStringConverter()は拡張点のひとつとなっていて、任意の QueryStringConverterを返すことが出来\る。WebScriptEnablingBehaviorはこれを拡張して JsonQueryStringConverterを返すようになっている(はずだ)。

ランタイムメソッドの実行とWebOperationContext

リクエストパラメータがランタイムオブジェクトに変換されたら、ようやくサービスメソッドを呼び出すことが出来る(この呼び出しはWCFのコアで行われる)。

WCFのコア部分では、サービス実行中にOperationContext.Currentが適宜生成され、サービスメソッド上でユーザが IExtensionを通じて何らかの拡張動作を制御することができる。WebHttpBinding の場合は、WebOperationContextを通じてこれを参照または制御できる。

このWebOperationContextが生成されるタイミングは必ずしも自明ではないが、monoでは IDispatchMessageFormatterのDeserializeRequest()で生成し、SerializeReply()の呼び出し後に解放している。いずれにせよ、サービスメソッド呼び出し中は存在していることになる。

ユーザはコード上で特にOutgoingRequestやOutgoingResponseを操作することができる。 IDispatchMessageFormatter.SerializeReply()では、OutgoingRequestに加えられた変更を、戻り値となるMessageに追加されるHttpResponseMessagePropertyに反映している。

HTTPレスポンスの生成

サービスメソッド呼び出しの結果は、IDispatchMessageFormatter.SerializerReply()によってMessageに変換される。ここでは、レスポンスフォーマットに応じて、使用するXmlObjectSerializerが変わる。レスポンスフォーマットがJSONであれば、DataContractJsonSerializerが使用される。XMLであれば通常のDataContractSerializerが使用される(XmlSerializerが使用される場合があるかもしれない)。返されるMessageにはWebContentFormatをもつ WebBodyFormatMessagePropertyが追加される。

生成された応答Messageは、HttpReplyChannelからGetRequestContext()で返されたRequestContext のReply()によって送信される。この中では、WebMessageEncodingBindingElementから生成された MessageEncoderが用いられ、Messageが(HTTPレスポンスの)Streamに出力される。

MessageEncoderの中では、そのMessageがもつWebBodyFormatMessagePropertyのFormatの値によって、利用するXmlReaderあるいはXmlWriterが異なってくる。具体的には、JSONで応答が返される場合は、 JsonReaderWriterFactory.CreateWriter()を使用して返されたXmlWriterを使って、XML的な構造化ツリーをJSONの形で出力することになる。