WebRequestDeath
この記事は、「WEB+DB PRESS Vol.17」(技術評論社)に掲載していただいた記事の一部を修正/加筆の上で公開するものです。
●Webサービスとのセッションタイムアウトについて
.NET Framework 1.1が生成するSOAPプロクシ(SOAP呼び出しのラッパー)の同期メソッドには問題があるように見えます。この問題について筆者が調査した範囲での状況と解決策について解説します。
問題は、Webサービス呼出し後アプリケーションを10数分放置すると2度目以降の投稿がすべてタイムアウトするという現象として表れました
HTTP1.1の既定の動作では明示的にクライアントがコネクションのクローズを要求しない限り、サーバーはセッションタイムアウト時間になるまでコネクションを維持します(図)。
図)SOAPプロクシとIISのTCPレベルのチャート ただし()はHTTP層 クライアント サーバ 状態 SYN → ← SYN/ACK ACK → コネクション確立 (SOAPリクエスト) → セッション開始 ← (SOAPレスポンス) アイドル セッションタイムアウト ← RST コネクション打ち切り
この状態でアプリケーションが次のSOAPリクエストを要求すれば、再度コネクションを確立しなければなりません。しかし、RST受信後サーバーに対していかなるパケットも送信されないのです。どうもWebRequestが、RSTによってスラムシャットダウン(コネクション打ち切り)されたソケットを利用し続けようとしているように見えます。
この状態は、アプリケーションからは対応できません。SOAPクライアントのインスタンスを作成し直してみましたが、集約しているWebRequestクラスのインスタンスは勝手に再利用されてしまうようです。しかも、SOAPプロクシが集約しているWebRequestクラスを外部から操作するメソッドは用意されていません。
HTTP1.1で、クライアントからサーバーに対して明示的にコネクションをクローズさせるには、HTTPヘッダのConnectionメタデータに値Closeを設定します(図)。
図)クライアントから明示的なクローズを要求 クライアント サーバ 状態 SYN → ← SYN/ACK ACK → コネクション確立 (SOAPリクエスト Connection:Close) → セッション開始 ← (SOAPレスポンス) セッション終了 ← FIN ACK → FIN → ← ACK コネクション終結
WebRequestクラスを直接操作して試すと、FINによってコネクションが終結した場合には、次の要求で正しくコネクションが再確立されます。これなら回避できます。すなわち、アプリケーションが明示的に切断を指示すれば良いわけです。なお、Connectionメタデータの設定は、HttpWebRequestクラスのKeepAliveプロパティで行います。KeepAliveプロパティにfalse(デフォルトはtrue)を設定すればConnectionメタデータに値Closeが設定されます。
ここでは問題となったとは言え、コネクションを要求の都度クローズすることは必ずしも望ましい動作ではありません。要求の都度クローズすることは、リクエスト/レスポンスの都度3ウェイハンドシェイク(TCPコネクションの確立)やコネクション終結処理、その後のコネクションの保持と監視を実行するといったオーバーヘッドがあるからです。
残る問題は、SOAPプロクシが集約しているWebRequestのインスタンスを操作することです。MSDNを調べると、SOAPプロクシがWebRequestのインスタンスを取得するメソッド(SoapHttpClientProtocol.GetWebRequest)は、protectedメソッドとして定義されています。そこで、SOAPプロクシを継承したクラスを用意し、必要に応じてKeepAliveプロパティを設定するようにしました(リスト310)。
リスト310 private class Contributor : ContributionService { bool last; internal Contributor() { last = false; } protected override WebRequest GetWebRequest(Uri uri) { HttpWebRequest wr = (HttpWebRequest)WebRequest.Create(uri); if (last) { wr.KeepAlive = false; } return wr; } internal bool Last { get { return last; } set { last = value; } } }
この結果、次のように処理されるようになり、問題は解消しました(図)。
図)修正された動作 クライアント サーバ 状態 SYN → ← SYN/ACK ACK → コネクション確立 (SOAPリクエスト) → セッション開始 ← (SOAPレスポンス) (SOAPリクエスト Connection:Close) → ← (SOAPレスポンス) セッション終了 ← FIN ACK → FIN → ← ACK コネクション終結
教訓は、.NET Frameworkはほとんどの処理について詳細を知らなくても簡単に利用できるようになっていますが、それでも開発者はネットワークやプロトコルの知識を持つべきだ、ということです。また、パケットキャプチャツールは必携でしょう。
Keyword(s):
References: