HttpWebRequest#AllowWriteStreamBufferingプロパティとHTTPヘッダ

このプロパティ、ドキュメントには書いていないが、エンドポイントから送信するデータをメモリ上にバッファリングするか否かを指定することで、同クラスのSendChunkedプロパティ又はContentLengthプロパティと連動するので注意が必要だ。

というのも、消費メモリを少しでも節約しようと標題のプロパティをfalseに設定して、サーバ(Tomcat)とXML(XMLoverHTTP)通信すると以下の例外が発生したからだ。

Exception: System.Net.ProtocolViolationException
Message: AllowWriteStreamBuffering が無効にされているときに書込み操作を実行するには、ContentLength を負でない数に設定するか、または SendChunked を true に設定する必要があります。
Source: System
   場所 System.Net.HttpWebRequest.CheckProtocol(Boolean onRequestStream)
   場所 System.Net.HttpWebRequest.GetRequestStream()

どういうことかさっぱりわからん。HTTPを抽象化したクラスでは、よくこういうことが起きる。
こういう時は実際に通信に使用しているHTTPヘッダを見るに限る。同プロパティによってHTTPプロトコルヘッダがどのように変わるかを見てみよう。

AllowWriteStreamBuffering = true, SendChunked = false で通信(HttpWebRequestクラスのデフォルト)

POST /webapp/HogeAction HTTP/1.1
Content-Type: text/xml
Accept-Encoding: gzip
Host: Kazzz
Content-Length: XXXX  ← 送信するデータのデータ長がセットされる
Expect: 100-continue
Proxy-Connection: Keep-Alive

なるほど、AllowWriteStreamBuffering = trueの場合は送信するデータを一度メモリに格納するため、Content-Lengthヘッダをセットできるのだ。では、falseの場合も見てみよう。SendChunked = trueというプロパティからしてHTTPプロトコルに詳しい方であれば予想が付くと思う。

AllowWriteStreamBuffering = false, SendChunked = true で通信

POST /webapp/HogeAction HTTP/1.1
Content-Type: text/xml
Accept-Encoding: gzip
Host: Kazzz
Transfer-Encoding: chunked
Expect: 100-continue
Proxy-Connection: Keep-Alive

違いはContent-Lengthヘッダの代わりに"Transfer-Encoding: chunked"が出力される所だ。ちなみにContentLengthプロパティを設定することで、前者と同じヘッダ構成となる。(gzip圧縮を行っているのでデータ長はストリームからカウントする必要があるが)

ヘルプ、又はMSDNには同プロパティの設定時の注意として、わざわざ

実装時の注意 AllowWriteStreamBuffering を true に設定すると、大きなデータセットをアップロードするときに、パフォーマンス上の問題が生じることがあります。これは、データ バッファが、利用できるメモリをすべて使用してしまう可能性があるためです。

と書いているにのも関わらずtrueがデフォルトなのは認証情報をキャッシュできることもそうだが、サーバ側が必ずしもHTTP1.1チャンク通信に対応しているとは限らないからだろうか。

プログラマが気をつけなければならないのは、Webサービス等で大量のデータをクライアントから飛ばす際にヒープが足りなくなる可能性があることと、認証情報をキャッシュする必要が無く、サーバ側がチャンク(Transfer-Encoding: chunked)に対応しており十分に安定しているのであればスケールの為にこのプロパティをfalseにすることを選択肢に入れることだ。

追記

コメントで菊池さんに詳しく解説して頂いているので是非見て頂きたいが、HTTPChunkを使うことがスケーラビリティに繋がる訳ではないようだ。転送するデータを細かくちぎって分割転送するのが良いとは限らないのは、大量のスレッドを並行して動かした時に必ずしも期待したとおりの処理性能を出せないのと同じなのだろう。

この辺は是非自分で実際に試してみたいと思っている。