【问题标题】:Memory usage when creating and downloading zip archive as HttpContent将 zip 存档创建和下载为 HttpContent 时的内存使用情况
【发布时间】:2015-04-21 14:40:56
【问题描述】:

我有一个 web api GET 方法,它返回一个 zip 文件以供下载。以下是创建 zip 存档的代码:

var resultStream = new MemoryStream();    
using (var zipArchive = new ZipArchive(resultStream, ZipArchiveMode.Create, leaveOpen: true))
{
    foreach (var file in files)
    {
        zipArchive.CreateEntryFromFile(file.Path, file.Name, CompressionLevel.Optimal);
    }
}

以下是响应的填充方式:

var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new ByteArrayContent(resultStream.ToArray());
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = "export_" + DateTime.Now.ToString("dd-MM-yyyy_HH-mm-ss") + ".zip";
response.Content.Headers.ContentDisposition.CreationDate = DateTime.Now;
response.Content.Headers.ContentDisposition.Size = resultStream.Length;
response.Content.Headers.ContentLength = resultStream.Length;

上面的代码工作得很好,问题是它消耗了服务器上的大量内存,当然这取决于文件大小。我尝试将结果更改为StreamContent,但这不起作用,因为响应仅返回标头并最终超时。

所以这是我的问题:

  1. 有没有办法避免在内存中加载所有文件,而是在创建 zip 文件时发送它?
  2. 在这种情况下使用 StreamContent 是否更好,如果是,我需要进行哪些更改才能使其正常工作?
  3. 在每种情况下,缓冲如何影响内存消耗?我尝试按照建议的in this article 实现自定义IHostBufferPolicySelector 来禁用缓冲,但它似乎没有任何效果。
  4. 目前可以通过导航链接、使用HttpClient或通过AJAX请求调用api操作,因此任何解决方案都必须支持所有场景。

【问题讨论】:

  • 尝试使用 GZipStream 和 PushStreamContent
  • 你的服务器收到多少请求?
  • 你不能设置内容长度,所以需要启用块编码。
  • @AliHasan 最多 2-3 个并发请求。它是一个内部应用程序,因此使用 ByteArrayContent 仍然是一种选择。
  • 您是否有任何未处理的对象?

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


【解决方案1】:

改编自 Kudu 项目,一种使用 PushStreamContent 结合特定 DelegatingStream 包装器来流式传输 zip 存档的方法:

public static class ZipStreamContent
{
    public static PushStreamContent Create(string fileName, Action<ZipArchive> onZip)
    {
        var content = new PushStreamContent((outputStream, httpContent, transportContext) =>
        {
            using (var zip = new ZipArchive(new StreamWrapper(outputStream), ZipArchiveMode.Create, leaveOpen: false))
            {
                onZip(zip);
            }
        });
        content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
        content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
        content.Headers.ContentDisposition.FileName = fileName;
        return content;        
    }

    // this wraps the read-only HttpResponseStream to support ZipArchive Position getter.
    public class StreamWrapper : DelegatingStream
    {
        private long _position = 0;

        public StreamWrapper(Stream stream)
            : base(stream)
        {
        }

        public override long Position
        {
            get { return _position; }
            set { throw new NotSupportedException(); }
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            _position += count;
            base.Write(buffer, offset, count);
        }

        public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
        {
            _position += count;
            return base.BeginWrite(buffer, offset, count, callback, state);
        }
    }
}

对于您的情况,您可以使用如下:

var response = new HttpResponseMessage(HttpStatusCode.OK);
var response.Content = ZipStreamContent.Create(
    "export_" + DateTime.Now.ToString("dd-MM-yyyy_HH-mm-ss") + ".zip",
    zipArchive => {
        foreach (var file in files)
        {
            zipArchive.CreateEntryFromFile(file.Path, file.Name, CompressionLevel.Optimal);
        }        
    });

【讨论】:

  • 谢谢 Alex,什么是 DelegatingStream,我该如何使用它?
  • Err,你可能需要稍微修改一下:ASP.NET 堆栈中的一个internal 抽象类(命名空间System.Net.Http),你可以找到它的源代码here
  • 刚试了一下,看来PushStreamContent是个不错的解决方案,现在内存使用率很低。但是,每当 zip 方法中出现异常时,我都会遇到问题,响应失败但响应状态仍然是 200,不幸的是,这会破坏任何 AJAX 调用。
  • @elolos 我不确定。我不认为你可以在事后设置响应代码。如果你在构造函数中省略状态码,只做var response = new HttpResponseMessage()
  • 似乎没有将异常发送给客户端,因为它们发生在客户端收到 200 OK 响应之后。所以我担心即使从内存的角度来看这是一个很好的解决方案,但在我的代码必须使用的上下文中它并没有那么有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-06-18
  • 1970-01-01
  • 1970-01-01
  • 2015-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多