【问题标题】: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 文件。 (在流式压缩包的情况下,压缩包中的“文件表”最后发送而不是第一个发送)。
所以,这应该可行,但我也建议:
- 确保您的异常日志记录适用于
StartAsync 之后的异常。无法将错误详细信息返回给客户端,因此您必须依赖日志记录。
- 如果您控制客户端,请测试这种新的错误情况,看看您是否可以检测到它。如果使用该客户端无法检测到它,请确保您的代码验证 zip。
【解决方案2】:
对于此用例,有关 zip 文件格式的任何内容都不需要大量内存。所有文件都必须按顺序排列,最后有一个表格描述 zip 结构和文件偏移量。这使得可以非常有效地进行流式传输,而无需使用太多内存。
你可能不需要自己写这个,ZipStreamer 是你托管的一个微服务,它就是这样做的(披露,我是作者)。它旨在解决您遇到的确切问题,方法是在字节进入时立即将其流式传输,并具有固定的缓冲区大小以防止内存爆炸。它可以仅使用几 MB 内存并行传输数百个 zip 文件。
如果您需要将其作为应用程序的一部分,这里有一些建议。
- 禁用压缩将节省 CPU 和一些内存。根据您的文件,压缩可能不是主要好处(zip压缩后jpeg实际上会变大)。如果您只是为了将多个文件合并为一个而进行压缩,这将非常有帮助。但这并不能解释使用 GB 的内存。
- 确保您保留流内容的时间不会超过您需要的时间,看起来确实如此。按照@Stephen 建议的 StartAsync 尽快开始流式传输。