HttpWebResponse による「チャンク化された」応答の読み取り
質問
StreamReader を使用して HttpWebResponse の GetResponseStream() によって返されたストリームを読み取るときに、「チャンクされた」応答を読み取ることができません。
// response is an HttpWebResponse
StreamReader reader = new StreamReader(response.GetResponseStream());
string output = reader.ReadToEnd(); // throws exception...
とき reader.ReadToEnd()
メソッドが呼び出されると、次の System.IO.IOException が発生します。 トランスポート接続からデータを読み取ることができません:接続が閉じられました。
上記のコードは、サーバーが「非チャンク」応答を返した場合に問題なく動作します。
これを動作させることができた唯一の方法は、最初のリクエストに (デフォルトの HTTP/1.1 ではなく) HTTP/1.0 を使用することですが、これは不十分な回避策のように思えます。
何か案は?
@チャック
あなたのソリューションはかなりうまく機能します。最後の Read() でも同じ IOException がスローされます。しかし、StringBuilder の内容を検査すると、すべてのデータが受信されていることがわかります。したがって、おそらく Read() を try-catch でラップし、「エラー」を飲み込む必要があるだけです。
解決
「チャンク」応答でこれを試したことはありませんが、このようなものは機能しますか?
StringBuilder sb = new StringBuilder();
Byte[] buf = new byte[8192];
Stream resStream = response.GetResponseStream();
string tmpString = null;
int count = 0;
do
{
count = resStream.Read(buf, 0, buf.Length);
if(count != 0)
{
tmpString = Encoding.ASCII.GetString(buf, 0, count);
sb.Append(tmpString);
}
}while (count > 0);
他のヒント
私も同様の問題に取り組んでいます。.net HttpWebRequest および HttpWebRequest は、Cookie とリダイレクトを自動的に処理しますが、応答本文のチャンク化されたコンテンツは自動的に処理しません。
これはおそらく、チャンク化されたコンテンツには単純なデータ以上のデータが含まれる可能性があるためです (例:チャンク名、末尾のヘッダー)。
ストリームには必要以上のコンテンツが含まれているため、単にストリームを読み取って EOF 例外を無視するだけでは機能しません。ストリームにはチャンクが含まれ、各チャンクはサイズを宣言することから始まります。ストリームが最初から最後まで単純に読み取られる場合、最終データにはチャンク メタデータが含まれます (また、それが gzip 圧縮されたコンテンツの場合は、解凍時に CRC チェックに失敗します)。
この問題を解決するには、ストリームを手動で解析し、各チャンクからチャンク サイズ (CR LF デリミタも同様) を削除し、最後のチャンクを検出してチャンク データのみを保持する必要があります。おそらくこれを行うライブラリがどこかにあると思いますが、私はまだ見つけていません。
役立つリソース:
http://en.wikipedia.org/wiki/Chunked_transfer_encoding http://tools.ietf.org/html/rfc2616#section-3.6.1
Craig、読んでいるストリームを見ずにデバッグするのは少し難しいですが、count 変数の設定を次のように変更できるかもしれません。
count = resStream.Read(buf, 0, buf.Length-1);
これはちょっとしたハックですが、最後の読み取りでエラーが発生し、データが返されない場合は、理論的にはこれで問題を回避できます。なぜストリームがそのようなことをするのか今でも疑問に思っています。
私も同じ問題を抱えていました(これが私がここにたどり着いた経緯です:-)。最終的に、チャンク化されたストリームが有効ではない、つまり最後の長さ 0 のチャンクが欠落しているという事実が突き止められました。有効なチャンクストリームと無効なチャンクストリームの両方を処理する次のコードを思いつきました。
using (StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
{
StringBuilder sb = new StringBuilder();
try
{
while (!sr.EndOfStream)
{
sb.Append((char)sr.Read());
}
}
catch (System.IO.IOException)
{ }
string content = sb.ToString();
}