Как доставить большие файлы в ASP.NET Response?
Я не ищу альтернативу содержимого потокового файла из базы данных, действительно, я ищу корень проблемы, это было запуск файла до IIS 6, где мы запускали наше приложение в классическом режиме, теперь мы обновил наш IIS до 7, и мы запускаем пул приложений в режиме конвейера и эта проблема началась.
У меня есть обработчик, где я должен доставлять большие файлы в запрос клиента. И я сталкиваюсь с следующими проблемами:
Файлы имеют средний размер от 4 до 100 МБ, поэтому рассмотрим 80-мегабайтный файл для загрузки файлов.
Буферизация включена, Медленный старт
Response.BufferOutput = True;
Это приводит к очень медленному запуску файла, так как загрузка пользователя и даже индикатор выполнения не отображаются до нескольких секунд, обычно от 3 до 20 секунд, причина в том, что IIS сначала считывает весь файл, определяет длину содержимого, а затем начинает передача файла. Файл воспроизводится в видеоплеере, и он работает очень медленно, однако iPad сначала загружает часть файла, чтобы он работал быстро.
Буферизация выключена, нет длины контента, быстрого запуска, отсутствия прогресса
Reponse.BufferOutput = False;
Это приводит к немедленному запуску, однако конечный клиент (типичный браузер, такой как Chrome) не знает Content-Length, поскольку IIS тоже не знает, поэтому он не отображает прогресс, вместо этого он говорит, что загружен X KB.
Буферизация, Ручная длина содержимого, Быстрый старт, Прогресс и нарушение протокола
Response.BufferOutput = False;
Response.AddHeader("Content-Length", file.Length);
Это приводит к правильной немедленной загрузке файлов в Chrome и т.д., однако в некоторых случаях обработчик IIS приводит к ошибке "Удаленное закрытие клиента" (это очень часто), а другие результаты WebClient приводят к нарушению протокола. Это происходит от 5 до 10% всех запросов, а не от всех запросов.
Я предполагаю, что происходит, IIS не посылает ничего, называемое 100 continue, когда мы не делаем буферизации, и клиент может отключиться, не ожидая выхода. Однако чтение файлов из источника может занять больше времени, но на стороне клиента я увеличил тайм-аут, но, похоже, отключен от IIS и не контролирует.
В любом случае я могу заставить Response отправить 100 continue и не позволить кому-либо закрыть соединение?
UPDATE
Я нашел следующие заголовки в Firefox/Chrome, здесь нет ничего необычного для нарушения протокола или плохого заголовка.
Access-Control-Allow-Headers:*
Access-Control-Allow-Methods:POST, GET, OPTIONS
Access-Control-Allow-Origin:*
Access-Control-Max-Age:1728000
Cache-Control:private
Content-Disposition:attachment; filename="24.jpg"
Content-Length:22355
Content-Type:image/pjpeg
Date:Wed, 07 Mar 2012 13:40:26 GMT
Server:Microsoft-IIS/7.5
X-AspNet-Version:4.0.30319
X-Powered-By:ASP.NET
ОБНОВЛЕНИЕ 2
Turning Recycling все еще не предлагал многого, но я увеличил MaxWorkerProcess до 8, и теперь я получаю меньше ошибок, чем раньше.
Но в среднем из 200 запросов за одну секунду от 2 до 10 запросов терпят неудачу.., и это происходит почти каждые секунды.
ОБНОВЛЕНИЕ 3
Продолжение 5% запросов с ошибкой "Сервер зафиксировал нарушение протокола. Раздел = ResponseStatusLine", у меня есть другая программа, которая загружает контент с веб-сервера, который использует WebClient, и который дает эту ошибку 4-5 раз в секунду, в среднем у меня 5% запросов не удается. Есть ли способ отслеживать сбой WebClient?
Проблемы с переопределением
Полученный файл с нулевым байтом
IIS закрывает соединение по какой-либо причине, на стороне клиента в WebConfig, я получаю 0 байтов для файла, который не равен нулю, мы делаем проверку SHA1 хешей, это говорит нам, что на веб-сервере IIS ошибка не записывается.
Это была моя ошибка, и она была решена, поскольку мы используем Entity Framework, она читала грязные (незафиксированные строки), поскольку чтение не было в области транзакций, поэтому в область транзакции была разрешена эта проблема.
Исключено нарушение правил протокола
WebClient выдает сообщение WebException: "Сервер совершил нарушение протокола. Раздел = ResponseStatusLine.
Я знаю, что могу включить небезопасный синтаксический анализ заголовков, но это не так, когда это мой обработчик HTTP, который отправляет соответствующие заголовки, не знаю, почему IIS отправляет что-нибудь лишнее (проверено на firefox и chrome, ничего необычного), это происходит только 2% раз.
ОБНОВЛЕНИЕ 4
Обнаружена ошибка sc-win32 64, и я где-то читал, что WebLimits для MinBytesPerSecond должен быть изменен с 240 на 0, но все одинаково. Однако я заметил, что всякий раз, когда IIS регистрирует 64 ошибки sc-win32, IIS регистрирует статус HTTP как 200, но была некоторая ошибка. Теперь я не могу включить Failed Trace Logging для 200, потому что это приведет к массивным файлам.
Обе указанные проблемы были решены путем увеличения MinBytesPerSecond, а также отключения сеансов, я добавил подробный ответ, суммирующий каждую точку.
Ответы
Ответ 1
Хотя правильный способ доставки больших файлов в IIS - это следующий вариант,
- Установите MinBytesPerSecond в Zero в WebLimits (это, безусловно, поможет повысить производительность, так как IIS выбирает закрыть клиентов, поддерживающих соединения KeepAlive с меньшими размерами).
- Выделите больше рабочего процесса в пул приложений, я установил 8, теперь это нужно делать, только если ваш сервер распространяет большие файлы. Это, безусловно, приведет к тому, что другие сайты будут работать медленнее, но это обеспечит лучшие поставки. Мы установили 8, поскольку у этого сервера есть только один веб-сайт, и он просто доставляет огромные файлы.
- Отключение утилизации пула приложений.
- Отключить сеансы
- Оставить буферизацию включен
- Перед каждым из следующих шагов проверьте, истинно ли Response.IsClientConnected, иначе откажитесь и ничего не отправляйте.
- Установить Content-Length перед отправкой файла
- Отметьте ответ
- Записывать в выходной поток и выполнять регулярные интервалы
Ответ 2
Когда вы задали длину содержимого с bufferOutput равным false, то возможная причина сбоя заключается в том, что IIS пытается gzip отправленный вами файл, и, установив Content-Length IIS, он не сможет изменить его на сжатый, и ошибки начинаются (*).
So keep the BufferOutput to false, and second disable the gzip from iis for the files you send
- или отключите iz gzip для всех файлов, и вы программно обрабатываете часть gzip, сохраняя вне gzip файлы, которые вы отправляете.
Некоторые аналогичные вопросы по той же причине:
Сайт ASP.NET иногда замерзает и/или показывает нечетный текст в верхней части страницы при загрузке на серверах с балансировкой нагрузки
Сжатие HTTP: некоторые внешние скрипты /CSS не декомпрессируются должным образом в течение некоторого времени
(*) почему бы не изменить его снова? потому что с момента установки заголовка вы не можете принять участие, за исключением случаев, когда вы включили эту опцию в IIS и принимаете случай, когда заголовок не все готов к отправке в браузер.
Последующие действия
Если бы не gziped, следующее, что мне пришло в голову, это то, что файл отправлен и по какой-то причине соединение задерживается, и получил тайм-аут и закрыт. Таким образом, вы получаете "Remote Host Closed The Connection".
Это можно решить в зависимости от причины:
- Клиент действительно закрыл соединение
- Тайм-аут выполняется из самой страницы, если вы используете обработчик (опять же, вероятно, сообщение должно быть "Тайм-аут страницы" ).
- Тайм-аут исходит из ожидания ожидания, страница занимает больше времени выполнения, получает тайм-аут и закрывает соединение. Возможно, в этом случае сообщение было отключено.
- Пул сделает переработку в момент отправки файла. Отключите все повторные циклы! Это самые возможные случаи, о которых я могу сейчас подумать.
Если он исходит из IIS, перейдите к свойствам веб-сайта и убедитесь, что вы установили самый большой "Тайм-аут подключения" и "Включить HTTP-Keep-Alives".
Тайм-аут страницы, изменив файл web.config(вы можете изменить его программно только для одной конкретной страницы)
<httpRuntime executionTimeout="43200"
Также посмотрите:
http://weblogs.asp.net/aghausman/archive/2009/02/20/prevent-request-timeout-in-asp-net.aspx
Замок сеанса
Еще одна вещь, которую вам нужно изучить, - не использовать сеанс на обработчике, который вы используете для отправки файла, потому что сеанс блокирует действие до завершения и если пользователь занимает больше времени для загрузки файла, второй можно получить время.
некоторый относительный:
вызвать страницу aspx, чтобы вернуть изображение в случайном порядке медленнее
Замена сеанса ASP.Net целиком
Функция Response.WriteFile завершается с ошибкой и дает 504 тайм-аут шлюза
Ответ 3
Что бы я сделал, это использовать не столь известный ASP.NET Response.TransmitFile, так как он очень быстро (и, возможно, использует Кеш кеша IIS) и заботится обо всех материалах заголовка. Он основан на неуправляемом Windows TransmitFile API.
Но чтобы иметь возможность использовать этот API, вам нужен физический файл для передачи. Итак, вот псевдо-код С#, который объясняет, как это сделать с вымышленным файловым файлом myCacheFilePath. Он также поддерживает возможности кэширования клиентов. Конечно, если у вас уже есть файл под рукой, вам не нужно создавать этот кеш:
if (!File.Exists(myCacheFilePath))
{
LoadMyCache(...); // saves the file to disk. don't do this if your source is already a physical file (not stored in a db for example).
}
// we suppose user-agent (browser) cache is enabled
// check appropriate If-Modified-Since header
DateTime ifModifiedSince = DateTime.MaxValue;
string ifm = context.Request.Headers["If-Modified-Since"];
if (!string.IsNullOrEmpty(ifm))
{
try
{
ifModifiedSince = DateTime.Parse(ifm, DateTimeFormatInfo.InvariantInfo);
}
catch
{
// do nothing
}
// file has not changed, just send this information but truncate milliseconds
if (ifModifiedSince == TruncateMilliseconds(File.GetLastWriteTime(myCacheFilePath)))
{
ResponseWriteNotModified(...); // HTTP 304
return;
}
}
Response.ContentType = contentType; // set your file content type here
Response.AddHeader("Last-Modified", File.GetLastWriteTimeUtc(myCacheFilePath).ToString("r", DateTimeFormatInfo.InvariantInfo)); // tell the client to cache that file
// this API uses windows lower levels directly and is not memory/cpu intensive on Windows platform to send one file. It also caches files in the kernel.
Response.TransmitFile(myCacheFilePath)
Ответ 4
Эта часть кода работает для меня.
Он немедленно запускает поток данных клиенту.
Он показывает прогресс во время загрузки.
Он не нарушает HTTP. Контент-длина заголовка указан и кодировка передачи chuncked не используется.
protected void PrepareResponseStream(string clientFileName, HttpContext context, long sourceStreamLength)
{
context.Response.ClearHeaders();
context.Response.Clear();
context.Response.ContentType = "application/pdf";
context.Response.AddHeader("Content-Disposition", string.Format("filename=\"{0}\"", clientFileName));
//set cachebility to private to allow IE to download it via HTTPS. Otherwise it might refuse it
//see reason for HttpCacheability.Private at http://support.microsoft.com/kb/812935
context.Response.Cache.SetCacheability(HttpCacheability.Private);
context.Response.Buffer = false;
context.Response.BufferOutput = false;
context.Response.AddHeader("Content-Length", sourceStreamLength.ToString (System.Globalization.CultureInfo.InvariantCulture));
}
protected void WriteDataToOutputStream(Stream sourceStream, long sourceStreamLength, string clientFileName, HttpContext context)
{
PrepareResponseStream(clientFileName, context, sourceStreamLength);
const int BlockSize = 4 * 1024 * 1024;
byte[] buffer = new byte[BlockSize];
int bytesRead;
Stream outStream = m_Context.Response.OutputStream;
while ((bytesRead = sourceStream.Read(buffer, 0, BlockSize)) > 0)
{
outStream.Write(buffer, 0, bytesRead);
}
outStream.Flush();
}