【问题标题】:Why is ASP.NET replacing a Content-Length header with a Transfer-Encoding header when manually flushing a response?为什么 ASP.NET 在手动刷新响应时将 Content-Length 标头替换为 Transfer-Encoding 标头?
【发布时间】:2011-12-20 22:10:16
【问题描述】:

我们的 Web 应用程序(ASP.NET Web 表单)有一个页面,该页面将向用户显示最近生成的 PDF 文件。由于 PDF 文件有时非常大,我们实施了一种“流式传输”方法,将其分块发送到客户端浏览器。

尽管以块的形式向下发送数据,但我们在发送文件之前就知道文件的完整大小,因此我们适当地设置了 Content-Length 标头。直到今天,这已经在我们的生产环境中运行了一段时间(并且在我们的测试环境中继续使用几乎相同的配置)。报告的问题是 Chrome 会尝试打开 PDF 文件,但会因“加载”动画卡住而挂起。

因为在我们的测试环境中一切正常,所以我能够使用 Firebug 查看在两个环境中返回的响应标头。在测试环境中,我看到了一个正确的“Content-Length”标头,而在生产环境中,它已被替换为 Transfer-Encoding: chunked 标头。 Chrome 不喜欢这样,因此挂断了。

我已经阅读了一些文章和帖子,这些文章和帖子讨论了在没有提供 Content-Length 标头时如何显示 Transfer-Encoding 标头,但是我们指定了 Content-Length 标头,并且在运行相同的标头时一切似乎仍然有效测试服务器上相同 PDF 文件的代码。

测试和生产服务器都运行 IIS 7.5,并且都启用了动态和静态压缩。

这里是有问题的代码:

var fileInfo = new FileInfo(fileToSendDown);
Response.ClearHeaders();
Response.ContentType = "application/pdf";            
Response.AddHeader("Content-Disposition", "filename=test.pdf");
Response.AddHeader("Content-Length", fileInfo.Length.ToString());
var buffer = new byte[1024];
using (var fs = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    int read;
    while ((read = fs.Read(buffer, 0, 1024)) > 0)
    {
        if (!response.IsClientConnected) break;
        Response.OutputStream.Write(buffer, 0, read);
        Response.Flush();
    }
}

我很幸运地在我的本地工作站上看到了相同的行为,因此使用调试器我能够看到“Transfer-Encoding: chunked”标头在调用期间通过 while 循环的第二次设置'冲洗'。此时,响应同时具有 Content-Length 标头和 Transfer-Encoding 标头,但不知何故,当响应到达浏览器时,Firebug 仅显示 Transfer-Encoding 标头。

更新

我想我已经跟踪到使用“块”中发送数据并将“过滤器”附加到 HttpResponse 对象的组合(我们使用过滤器来跟踪被发送到的视图状态的大小)每页)。在将 PDF 发送到浏览器时,我们使用 HTTP 过滤器没有任何意义,因此在此处清除过滤器已经解决了我们的问题。纯粹出于好奇,我决定深入挖掘一下,如果其他人将来偶然发现这个问题,我会更新这个问题。

我在 AppHarbor 上有一个简单的应用程序可以重现该问题:http://transferencodingtest.apphb.com/。如果您同时选中“使用过滤器?”和“发送块?”框,您应该能够看到“传输编码:分块”标题出现(使用 Chrome 开发工具、Firebug、Fiddler 等)。如果未选中其中任何一个框,您将获得正确的内容长度标头。底层代码在 github 上,所以你可以看到幕后发生的事情:

https://github.com/appakz/TransferEncodingTest

请注意,要在本地复制,您需要在 IIS 7.5 中设置本地网站(7 也可以工作,我没有尝试过)。 Visual Studio 附带的 ASP .NET 开发服务器不会重现该问题。

我在此处的博客文章中添加了更多详细信息:'Content-Length' Header Replaced With 'Transfer-Encoding: Chunked' in ASP .NET

【问题讨论】:

  • 我知道这是一个老问题,但是在做类似的研究时,我遇到了this excellent post 解释这个问题。产生这个问题需要一组特定的环境。希望这对未来的读者有所帮助。
  • 那是我在提出问题后写的帖子,没有得到答案,然后终于找出了一些步骤来持续重现它。我用复制它的示例应用程序的链接更新了原始问题,但我想我也应该添加一个指向博客文章的链接。

标签: asp.net iis-7.5


【解决方案1】:

an article on MSDN看来你可以禁用分块编码:

appcmd set config /section:asp /enableChunkedEncoding:False

但它在ASP settings 下被提及,因此它可能不适用于从 ASP.NET 处理程序生成的响应。

【讨论】:

  • 我已经查看了这些设置,但它们对问题没有影响。
【解决方案2】:

一旦调用了Response.Flush(),响应体就在被发送到客户端的过程中,所以不能在响应中添加额外的头部。我发现当时对Response.Flush() 的第二次调用不太可能添加Transfer-Encoding 标头。

你说你启用了压缩。这几乎总是需要分块响应。因此,如果服务器在压缩之前知道Content-Length,它可能会用该标头替换Transfer-Encoding 标头并将响应分块,这是有道理的。但是,即使在服务器上启用了压缩,客户端也必须在其Accept-Encoding 请求标头中明确声明支持压缩,否则服务器无法压缩响应。你在测试中检查过吗?

最后一点,由于您手动调用Response.Flush(),请尝试设置Response.Buffer = TrueResponse.BufferOutput = False。显然,它们对Response.Flush() 的运作方式产生了相互矛盾的影响。见this pagethis page底部的评论。

【讨论】:

  • 我同意在第二次调用 Flush 时标题“改变”是没有任何意义的,但我已经尝试过同样的代码,有和没有以更小的块发送数据,并且浏览器仅在使用多次调用刷新时注册已收到“Transfer-Encoding”标头。我正在开发一个小型示例应用程序,它将在我拥有它时进行复制并发布。
【解决方案3】:

我在编写大型 CSV 时遇到了类似的问题(文件不存在,我通过迭代内存中的集合并生成行来逐行编写字符串),方法是在响应流上调用 Response.Write将 BufferOutput 设置为 false,但解决方案是更改

Reponse.ContentType = 'text/csv'Reponse.ContentType = 'application/octet-stream'

当内容类型未设置为 application/octet-stream 时,添加了一堆其他响应标头,例如 Content-Encoding - gzip

【讨论】:

    猜你喜欢
    • 2016-11-05
    • 2011-11-09
    • 2017-10-03
    • 1970-01-01
    • 2020-07-15
    • 1970-01-01
    • 2014-09-09
    • 2014-03-30
    相关资源
    最近更新 更多