【问题标题】:Stream on the fly zipped files to client via rest endpoint通过休息端点将压缩文件即时流式传输到客户端
【发布时间】:2022-11-10 01:40:26
【问题描述】:

我正在尝试实时流式传输压缩文件,但内存消耗很高。例如,要压缩 2.8 GB 的总文件大小,需要将近 5 GB 的处理器内存。

[Route("zip")]    
public class ZipController : ControllerBase
{
    private readonly HttpClient _httpClient;
    public ZipController()
    {
        _httpClient = new HttpClient();
    }

    [HttpPost]
    public async Task Zip([FromBody] JsonToZipInput input)
    {        

        Response.ContentType = "application/octet-stream";
        Response.Headers.Add($"Content-Disposition", $"attachment; filename=\"{input.FileName}\"");
    
        using var zipArchive =
            new ZipArchive(Response.BodyWriter.AsStream(), ZipArchiveMode.Create);
        foreach (var (key, value) in input.FilePathsToUrls)
        {
            var zipEntry = zipArchive.CreateEntry(key, CompressionLevel.Optimal);
            await using var zipStream = zipEntry.Open();
            await using var stream = await _httpClient.GetStreamAsync(value);
            await stream.CopyToAsync(zipStream);
        }

    }

}

【问题讨论】:

    标签: .net-core zip c#-ziparchive


    【解决方案1】:

    我相信你应该可以拨打Response.StartAsync

    [HttpPost]
    public async Task Zip([FromBody] JsonToZipInput input)
    {        
      Response.ContentType = "application/octet-stream";
      Response.Headers.Add($"Content-Disposition", $"attachment; filename="{input.FileName}"");
    
      await Response.StartAsync();
        
      using var zipArchive = new ZipArchive(Response.BodyWriter.AsStream(), ZipArchiveMode.Create);
      foreach (var (key, value) in input.FilePathsToUrls)
      {
        var zipEntry = zipArchive.CreateEntry(key, CompressionLevel.Optimal);
        await using var zipStream = zipEntry.Open();
        await using var stream = await _httpClient.GetStreamAsync(value);
        await stream.CopyToAsync(zipStream);
      }
    }
    

    StartAsync 应该开始发送响应。请注意,既不是响应头也不是状态码可以在调用 StartAsync 后进行修改。

    特别是,这意味着您的异常处理将有所不同。以前,异常(例如,来自请求中的错误 URL)会导致异常状态代码(即 500)。使用流式响应,StartAsync 之后的任何异常都无法更改状态码;它已经发送了。相反,它会在客户端看来好像连接在没有完全关闭的情况下终止。更复杂一点的是,这种行为对于 Web 服务器来说并不少见。成功的情况下,因此客户可能不会抱怨 - 他们最终会得到截断(无效)的 zip 文件。 (在流式压缩包的情况下,压缩包中的“文件表”最后发送而不是第一个发送)。

    所以,这应该可行,但我也建议:

    1. 确保您的异常日志记录适用于StartAsync 之后的异常。无法将错误详细信息返回给客户端,因此您必须依赖日志记录。
    2. 如果您控制客户端,请测试这种新的错误情况,看看您是否可以检测到它。如果使用该客户端无法检测到它,请确保您的代码验证 zip。

    【讨论】:

      【解决方案2】:

      对于此用例,有关 zip 文件格式的任何内容都不需要大量内存。所有文件都必须按顺序排列,最后有一个表格描述 zip 结构和文件偏移量。这使得可以非常有效地进行流式传输,而无需使用太多内存。

      你可能不需要自己写这个,ZipStreamer 是你托管的一个微服务,它就是这样做的(披露,我是作者)。它旨在解决您遇到的确切问题,方法是在字节进入时立即将其流式传输,并具有固定的缓冲区大小以防止内存爆炸。它可以仅使用几 MB 内存并行传输数百个 zip 文件。

      如果您需要将其作为应用程序的一部分,这里有一些建议。

      • 禁用压缩将节省 CPU 和一些内存。根据您的文件,压缩可能不是主要好处(zip压缩后jpeg实际上会变大)。如果您只是为了将多个文件合并为一个而进行压缩,这将非常有帮助。但这并不能解释使用 GB 的内存。
      • 确保您保留流内容的时间不会超过您需要的时间,看起来确实如此。按照@Stephen 建议的 StartAsync 尽快开始流式传输。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-12-21
        • 2015-11-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-05
        相关资源
        最近更新 更多