我们的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/。如果同时选中“使用过滤器?”和'发送大块?'您应该能够看到“transfer-encoding:chunked”标题显示的框(使用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
答案 0 :(得分:5)
从an article on MSDN开始,您似乎可以禁用分块编码:
appcmd set config /section:asp /enableChunkedEncoding:False
但它在ASP settings下提到,因此它可能不适用于从ASP.NET处理程序生成的响应。
答案 1 :(得分:4)
调用Response.Flush()
后,响应正文将被发送到客户端,因此不能向响应中添加其他标头。我发现第二次调用Response.Flush()
时不太可能在此时添加Transfer-Encoding
标题。
您说您已启用压缩功能。这几乎总是需要一个分块响应。因此,如果服务器在压缩之前知道Content-Length
,它可能会将该标头替换为Transfer-Encoding
标头并对响应进行分块,这是有意义的。但是,即使在服务器上启用了压缩,客户端也必须在其Accept-Encoding
请求标头中明确声明对压缩的支持,否则服务器无法压缩响应。你在考试中检查了吗?
最后一点,由于您是手动拨打Response.Flush()
,请尝试设置Response.Buffer = True
和Response.BufferOutput = False
。显然,他们对Response.Flush()
的运作方式产生了相互矛盾的影响。请参阅this page和this page底部的评论。
答案 2 :(得分:0)
当我写一个大型CSV时,我遇到了类似的问题(文件不存在,我通过迭代内存集合并生成行来逐行写一个字符串)通过调用Response.Write
BufferOutput设置为false的响应流,但解决方案是更改
Reponse.ContentType = 'text/csv'
至Reponse.ContentType = 'application/octet-stream'
当内容类型未设置为application / octet-stream时,会添加一堆其他响应标头,例如Content-Encoding - gzip