在 c# api 中转发流

Doc*_*Doc 0 c# api zip stream asp.net-core

在我的服务中,我们需要获取另一个服务创建的 zip 文件并将其返回。

这是我的代码(代码已针对问题进行了简化):

[HttpGet("mediafiles/{id}")]
public async Task<IActionResult> DownloadMediaFiles(int id)
{
    var fileIds = _myProvider.GetFileIdsForEntityId(id); // result be like "1,2,3,4"

    using var httpClient = new HttpClient();
    var response = await httpClient.GetAsync($"http://file-service/bulk/{fileIds}");
    var stream = await response.Content.ReadAsStreamAsync();

    return File(stream, "application/octet-stream", "media_files.zip");
}
Run Code Online (Sandbox Code Playgroud)

使用id我可以收集创建fileIds字符串所需的信息并调用其他服务。

这是其他服务上的api(已针对问题简化了代码):

[HttpGet("bulk/{idList}")]
public async Task<IActionResult> DownloadBulk(string idList)
{
    var ids = string.IsNullOrEmpty(idList) ? new List<int>() : idList.Split(',').Select(x => Convert.ToInt32(x));

    using var memoryStream = new MemoryStream();
    using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
    {
        var index = archive.CreateEntry("hello.txt");
        using (var entryStream = index.Open())
        using (var streamWriter = new StreamWriter(entryStream))
        {
            streamWriter.Write("hello");
        }
    }

    var byteArray = memoryStream.ToArray();

    return File(byteArray, "application/octet-stream", "media_files.zip");
}
Run Code Online (Sandbox Code Playgroud)

但是当客户试图打开 zip 我们得到

发生异常。ArchiveException (FormatException: 找不到中央目录记录的结尾)

我对这两行完全没有信心 /mediafiles/{id}

var stream = await response.Content.ReadAsStreamAsync();

return File(stream, "application/octet-stream", "media_files.zip");
Run Code Online (Sandbox Code Playgroud)

可能问题就在那里。

我只需要转发file-service回复,但我不知道为什么

Xer*_*lio 5

我相信您遇到的问题是,DownloadMediaFiles(int id)您正在使用HttpClient离开函数作用域时被处理的 。在stream从因此被关闭和处置以及响应创建之前,响应有效载荷已经完成写入其内容到客户端。因此,客户端会收到一个您无法打开的不完整 zip 文件。请参阅此处以供参考。

这个答案中,您可以使用一个简单的解决方案,它只是将响应流(来自 的响应流$"http://file-service/bulk/{fileIds}")读取到一个字节数组中,然后将其传递给客户端的响应:

using var httpClient = new HttpClient();
var response = await httpClient.GetAsync($"http://file-service/bulk/{fileIds}");
var byteArr = await response.Content.ReadAsByteArrayAsync();

return File(byteArr, "application/octet-stream", "media_files.zip");
Run Code Online (Sandbox Code Playgroud)

您可能会意识到这意味着将整个文件加载到内存中,如果您计划处理大文件,或者如果 API 应该被许多客户端同时使用,甚至处理中等大小的文件,这很快就会成为一个问题。您的 Web 应用程序很可能会在某个时候耗尽内存。

相反,我看到了这篇文章,它展示了如何使用HttpClient. 您应该能够坚持该文章的第一部分(所有 ZIP 文件和基于回调的响应内容都无关紧要)。

要回顾那篇文章,您只需要如下内容:

// Your ControllerClass.cs

private static HttpClient Client { get; } = new HttpClient();

[HttpGet("mediafiles/{id}")]
public async Task<IActionResult> DownloadMediaFiles(int id)
{
    var fileIds = _myProvider.GetFileIdsForEntityId(id); // result be like "1,2,3,4"
    var stream = await Client.GetStreamAsync($"http://file-service/bulk/{fileIds}");

    return File(stream, "application/octet-stream", "media_files.zip");
}
Run Code Online (Sandbox Code Playgroud)

您会注意到,该stream对象并未在此处处理,但ASP.Net Core 会为您执行此操作,作为将响应负载写入客户端的一部分。Client存储在静态全局变量中的which 也不会被处理,这意味着您可以在请求之间重用它(通常建议不要在HttpClient每次需要时实例化一个新的)。ASP.Net Core 2.1 及更高版本特别支持通过IHttpClientFactory接口为您注入客户端的依赖。我建议你这样做而不是静态变量。阅读此处了解注入客户端工厂的最基本用法。

现在,您应该能够直接从“其他服务”流式传输文件内容,而无需将其加载到 API Web 应用程序的内存中。