使用 HttpWebResponse 读取“分块”响应
Posted
技术标签:
【中文标题】使用 HttpWebResponse 读取“分块”响应【英文标题】:Reading "chunked" response with HttpWebResponse 【发布时间】:2010-09-06 05:31:38 【问题描述】:使用 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.0(而不是默认的 HTTP/1.1),但这似乎是一个蹩脚的解决方法。
有什么想法吗?
@查克
您的解决方案效果很好。它仍然在最后一个 Read() 上抛出相同的 IOExeception。但是在检查了 StringBuilder 的内容之后,看起来所有的数据都已经收到了。所以也许我只需要将 Read() 包装在 try-catch 中并吞下“错误”。
【问题讨论】:
要读取分块响应,您需要关注en.wikipedia.org/wiki/Chunked_transfer_encoding 我在 .NET 4.6 连接到 PowerDNS 3.4.5 HTTP REST API 时看到了这种行为。变通办法没有帮助。如果我吞下异常,我会丢失部分响应。 【参考方案1】:还没有尝试过使用“分块”响应,但这样的东西会起作用吗?
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);
【讨论】:
这对于多字节编码(即非 ASCII)是危险的,因为无法保证读取将与字符边界对齐。 @Chuck 你不能只使用 ASCII,你需要弄清楚实际使用的是什么编码,即通过 Content-Type,然后使用它来“GetString”【参考方案2】:我正在解决类似的问题。 .net HttpWebRequest 和 HttpWebRequest 会自动处理 cookie 和重定向,但它们不会自动处理响应正文上的分块内容。
这可能是因为分块内容可能包含的不仅仅是简单数据(即:块名称、尾随标头)。
仅仅读取流并忽略 EOF 异常是行不通的,因为流包含的内容多于所需的内容。流将包含块,每个块都以声明其大小开始。如果流只是从头到尾读取,最终数据将包含块元数据(如果是 gzip 内容,解压缩时将无法通过 CRC 检查)。
为了解决这个问题,需要手动解析流,从每个块中删除块大小(以及 CR LF 分隔符),检测最终块并仅保留块数据。那里可能有一个图书馆可以做到这一点,我还没有找到它。
有用的资源:
http://en.wikipedia.org/wiki/Chunked_transfer_encoding https://www.rfc-editor.org/rfc/rfc2616#section-3.6.1
【讨论】:
【参考方案3】:在尝试了 *** 和 Google 的大量 sn-ps 之后,最终我发现这是最好的(假设您知道数据是 UTF8 字符串,如果不知道,您可以保留字节数组并适当处理):
byte[] data;
var responseStream = response.GetResponseStream();
var reader = new StreamReader(responseStream, Encoding.UTF8);
data = Encoding.UTF8.GetBytes(reader.ReadToEnd());
return Encoding.Default.GetString(data.ToArray());
我发现其他变体大部分时间都有效,但偶尔会截断数据。我从以下位置获得了这个 sn-p:
https://social.msdn.microsoft.com/Forums/en-US/4f28d99d-9794-434b-8b78-7f9245c099c4/problems-with-httpwebrequest-and-transferencoding-chunked?forum=ncl
【讨论】:
【参考方案4】:这很有趣。在使用请求标头并删除“Accept-Encoding: gzip,deflate”期间,我用例中的服务器确实以纯 ascii 方式回答,不再使用分块、编码的 sn-ps。也许你应该试一试,让“Accept-Encoding: gzip,deflate”远离。这个想法是在阅读上面提到的关于使用压缩的主题的 wiki 时产生的。
【讨论】:
【参考方案5】:我也遇到了同样的问题(这就是我来到这里的原因:-)。最终追查到分块流无效的事实 - 最后的零长度块丢失了。我想出了以下代码来处理有效和无效的分块流。
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();
【讨论】:
将字节转换为 char 是危险的,因为它完全忽略了多字节编码。以上是关于使用 HttpWebResponse 读取“分块”响应的主要内容,如果未能解决你的问题,请参考以下文章