【问题标题】:C# Stream Response from 3rd party, minimal buffering来自第 3 方的 C# 流响应,最小缓冲
【发布时间】:2016-05-31 04:22:08
【问题描述】:

我们的 ASP.NET MVC 端点充当另一个第三方 HTTP 端点的代理,它返回动态生成的大约 400MB 的 XML 文档。

ASP.NET MVC 有没有办法通过“最小”缓冲将第 3 方响应直接“流式传输”到我们端点的用户?

目前,ASP.NET System.Web.Mvc.Controller.File() 似乎将整个文件加载到内存中作为响应。 除了内存使用量的跳跃之外,我不确定如何确认这一点?

System.Web.Mvc.Controller.File(

IIS AppPool 内存使用量增加了 400MB,之后被垃圾收集器回收。

如果我们可以避免 System.Web.Mvc.Controller.File() 将整个 400MB 字符串加载到内存中,通过“几乎直接”从传入的响应流式传输,那就太好了, 有可能吗?

mock c# linqpad 代码大致是这样的

public class MyResponseItem {
    public Stream myStream;
    public string metadata;
}

void Main()
{
    Stream stream = MyEndPoint();   

    //now let user download this XML as System.Web.Mvc.FileResult
    System.Web.Mvc.ActionResult fileResult = System.Web.Mvc.Controller.File(stream, "text/xml");
    fileResult.Dump();
}

Stream MyEndPoint() {
    MyResponseItem myResponse = GetStreamFromThirdParty("https://www.google.com");
    return myResponse.myStream;
}

MyResponseItem GetStreamFromThirdParty(string fullUrl)
{   
    MyResponseItem myResponse = new MyResponseItem();   
    System.Net.WebResponse webResponse = System.Net.WebRequest.Create(fullUrl).GetResponse();
    myResponse.myStream = webResponse.GetResponseStream();
    return myResponse;
}

【问题讨论】:

    标签: c# asp.net-mvc


    【解决方案1】:

    您可以通过不缓冲而直接将流复制到输出流来减少内存占用,这里是一个快速的 n' 脏示例:

        public async Task<ActionResult> Download()
        {
            using (var httpClient = new System.Net.Http.HttpClient())
            {
                using (
                    var stream = await httpClient.GetStreamAsync(
                        "https://ckannet-storage.commondatastorage.googleapis.com/2012-10-22T184507/aft4.tsv.gz"
                        ))
                {
                    Response.ContentType = "application/octet-stream";
                    Response.Buffer = false;
                    Response.BufferOutput = false;
                    await stream.CopyToAsync(Response.OutputStream);
                }
                return new HttpStatusCodeResult(200);
            }
        }
    

    如果您想进一步减少占用空间,可以使用 CopyToAsync(Stream, Int32) 重载设置较小的缓冲区大小,默认为 81920 字节。

    【讨论】:

    • 非常感谢 Matias,复制到 Response.OutputStream 就可以了。看起来 System.Web.Mvc.Controller.File() 是罪魁祸首,试图将所有内容加载到内存中
    【解决方案2】:

    我对代理下载的要求还需要确保源 ContentType(或您需要的任何 Header)也可以转发。 (例如,如果我代理下载http://techslides.com/sample-webm-ogg-and-mp4-video-files-for-html5 中的视频,我需要让用户在直接打开链接时看到相同的浏览器视频播放器屏幕,但不跳转到文件下载/硬编码 ContentType)

    根据@devlead 的回答+另一篇帖子https://stackoverflow.com/a/30164356/4684232,我调整了答案以满足我的需要。这是我调整后的代码,以防有人有同样的需求。

    public async Task<ActionResult> Download(string url)
    {
        using (var httpClient = new System.Net.Http.HttpClient())
        {
            using (var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
            {
                response.EnsureSuccessStatusCode();
                using (var stream = await response.Content.ReadAsStreamAsync())
                {
                    Response.ContentType = response.Content.Headers.ContentType.ToString();
                    Response.Buffer = false;
                    Response.BufferOutput = false;
                    await stream.CopyToAsync(Response.OutputStream);
                }
            }
    
            return new HttpStatusCodeResult(200);
        }
    }
    

    附言HttpCompletionOption.ResponseHeadersRead 是重要的性能关键。如果没有它,GetAsync 将一直等待,直到下载整个源响应流,这要慢得多。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-05-20
      • 1970-01-01
      • 2021-08-16
      • 1970-01-01
      • 1970-01-01
      • 2015-12-13
      • 1970-01-01
      • 2012-07-08
      相关资源
      最近更新 更多