使用Web API下载大文件返回OutOfMemoryException

时间:2017-12-05 20:20:17

标签: c# asp.net asp.net-web-api

我尝试在SO上使用了大量的问题/答案 - 但似乎无法克服我在尝试通过网络API下载200MB zip文件时收到的OutOfMemoryException

为了测试,我大大简化了我的代码:

    [HttpPost]
    public async Task<HttpResponseMessage> ExportReports(OrderExportFilter filterJson)
    {

        var filename = "C:\\pdftemp\\1128d0ff-a4b7-440d-9e3b-dd152445eb62.zip";

        var fileStream = File.OpenRead(filename);

        var content = new StreamContent(fileStream, 4096);

        resp.Content = content;

        resp.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
        resp.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
        {
            FileName = "OrdersExport.pdf"
        };

        return resp;
     }

这只是我尝试下载zip文件的众多方法之一,我还referenced this link并重复了代码无效。

我不知道我正在做什么错误,无法下载大型zip文件。

更新:评论中的每个请求我都试过使用“application / octet-stream”代替 - 仍然没有运气 - 同样的错误。

另外 - 还有一点需要注意 - 当我使用此代码下载较小的zip文件时,它工作正常,只是一旦文件太大就开始轰炸。

例外更新:

我能够提取异常细节:

    {
        "Message": "An error has occurred.",
        "ExceptionMessage": "Exception of type 'System.OutOfMemoryException' was thrown.",
        "ExceptionType": "System.OutOfMemoryException",
        "StackTrace": "   at System.IO.MemoryStream.set_Capacity(Int32 value)\r\n   at System.IO.MemoryStream.EnsureCapacity(Int32 value)\r\n   at System.IO.MemoryStream.Write(Byte[] buffer, Int32 offset, Int32 count)\r\n   at System.IO.Compression.DeflateStream.WriteDeflaterOutput(Boolean isAsync)\r\n   at System.IO.Compression.DeflateStream.PurgeBuffers(Boolean disposing)\r\n   at System.IO.Compression.DeflateStream.Dispose(Boolean disposing)\r\n   at System.IO.Stream.Close()\r\n   at System.IO.Compression.GZipStream.Dispose(Boolean disposing)\r\n   at System.IO.Stream.Close()\r\n   at System.IO.Stream.Dispose()\r\n   at System.Net.Http.Extensions.Compression.Core.Compressors.BaseCompressor.<Compress>d__4.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Net.Http.Extensions.Compression.Core.Models.CompressedContent.<SerializeToStreamAsync>d__4.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Owin.HttpMessageHandlerAdapter.<BufferResponseContentAsync>d__13.MoveNext()"
    }

1 个答案:

答案 0 :(得分:3)

您的堆栈跟踪表示关闭DeflateStream时抛出异常。此流显然写入MemoryStream并关闭DeflateStream输出对MemoryStream的整个(压缩)响应。也就是说,MemoryStream中的字节数组缓冲区被分配,如果压缩输出太大,则会失败并显示OutOfMemoryException

压缩响应缓存在内存中有点令人惊讶,因为Web服务器至少在概念上能够通过压缩器将数据从文件流传输到网络,而无需先将整个文件读取并压缩到内存中。

从评论中我猜你在Web API应用程序中使用了NuGet包Microsoft.AspNet.WebApi.Extensions.Compression.Server。虽然我不能声称我完全理解它是如何工作的,但我确实尝试对它进行反编译,从我可以看到通过这个中间件发送响应导致输出被缓冲不是一次但实际上是两次。 (BaseCompressor.Compress()方法写入由MemoryStream创建的StreamManager.GetStream(),然后将此MemoryStream读入一个字节数组,有效地使压缩器的内存需求加倍。)

根据此分析,您应该关闭针对大型响应的压缩,这似乎通过将属性[Compression(Enabled = false)]应用于您的控制器操作来解决您的问题。

有点讽刺的是,你无法压缩可能从压缩中受益最多的大回应。但是,如果您在本机支持响应压缩的IIS等Web服务器上运行Web API,则可以完全删除中间件,而是在Web服务器中打开压缩(在IIS中称为dynamic compression)。本机压缩可能也比托管代码中的压缩效果更好。