Ответ 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 будет ожидать, пока не будет загружен весь исходный поток ответа, что намного медленнее.