Ответ C# Stream от третьей стороны, минимальная буферизация

Наша конечная точка ASP.NET MVC ведет себя как прокси для сторонней конечной точки HTTP, которая возвращает динамически около 400 МБ XML-документа.

Есть ли способ для ASP.NET MVC "транслировать" этот ответ третьей стороны прямо пользователю нашей конечной точки с "минимальной" буферизацией?

На данный момент это выглядит как ASP.NET System.Web.Mvc.Controller.File() загружает весь файл в память в качестве ответа. Не знаете, как я могу это подтвердить, кроме скачка в использовании памяти?

System.Web.Mvc.Controller.File (

Использование памяти IIS AppPool увеличивается на 400 МБ, а затем повторно запрашивается сборщиком мусора.

Было бы хорошо, если бы мы могли избежать загрузки System.Web.Mvc.Controller.File () целыми 400-мегабайтными строками в память путем потоковой передачи "почти напрямую" из входящего ответа. Возможно ли это?

Код 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;
}

1 ответ

Решение

Вы можете уменьшить объем занимаемой памяти, не буферизуя и просто копируя поток непосредственно в выходной поток, вот быстрый и грязный пример этого:

    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 байт.

Мое требование к загрузке прокси также должно гарантировать, что исходный ContentType (или любой нужный заголовок) также может быть перенаправлен. (например, если я загружаю видео через прокси на http://techslides.com/sample-webm-ogg-and-mp4-video-files-for-html5, мне нужно позволить пользователю видеть тот же экран браузера-видеоплеера поскольку они открывают ссылку напрямую, но не переходят к типу ContentType для загрузки файлов / жестко запрограммированному)

Основываясь на ответе @devlead + еще одно сообщение /questions/19264513/httpclient-getstreamasync-i-http-kodyi-sostoyaniya/19264527#19264527, я скорректировал ответ, чтобы удовлетворить мою потребность. Вот мой скорректированный код на случай, если у кого-то возникнет такая же потребность.

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);
    }
}

ps HttpCompletionOption.ResponseHeadersRead - важный ключ производительности. Без него GetAsync будет ожидать, пока не будет загружен весь исходный поток ответа, что намного медленнее.

Другие вопросы по тегам