Как доставить большие файлы в ASP.NET Response? - PullRequest
41 голосов
/ 07 марта 2012

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

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

Включение рециркуляции по-прежнему мало что дает, но я увеличил свой MaxWorkerProcess до 8, и теперь я получаю меньше ошибок, чем раньше.

Но в среднем из 200 запросов в секунду от 2 до 10 запросов завершаются неудачно ... и это происходит почти каждые две секунды.

ОБНОВЛЕНИЕ 3

Продолжая 5% сбоев запросов с «Сервер совершил нарушение протокола. Section = ResponseStatusLine», у меня есть другая программа, которая загружает контент с веб-сервера, который использует WebClient, и который выдает эту ошибку 4-5 раз в секунду, на в среднем у меня 5% запросов не выполняются. Есть ли способ отследить сбой WebClient?

Переопределенные проблемы

Получен нулевой байт файл

IIS по какой-то причине закрывает соединение, на стороне клиента в WebConfig я получаю 0 байт для файла, который не является нулевым байтом, мы выполняем проверку хэша SHA1, это говорит нам о том, что на веб-сервере IIS ошибка не записывается.

Это была моя ошибка, и она была устранена, так как мы использовали Entity Framework, она считывала грязные (незафиксированные строки), так как считывание не входило в область транзакции, а помещение ее в область транзакции решило эту проблему.

Возникло исключение нарушения протокола

WebClient выдает WebException, говоря: «Сервер совершил нарушение протокола. Section = ResponseStatusLine.

Я знаю, что могу включить небезопасный разбор заголовков, но это не главное, когда мой HTTP-обработчик отправляет правильные заголовки, не знаю, почему IIS отправляет что-то дополнительное (проверено на firefox и chrome, ничего необычного), этопроисходит только 2% раз.

ОБНОВЛЕНИЕ 4

Обнаружена ошибка sc-win32 64, и я где-то читал, что WebLimits для MinBytesPerSecond должен быть изменен с 240 на 0, но все жевсе одинаково.Однако я заметил, что всякий раз, когда IIS регистрирует ошибку 64 sc-win32, IIS записывает HTTP Status как 200, но при этом возникает некоторая ошибка.Теперь я не могу включить ведение журнала Failed Trace Logging для 200, потому что это приведет к массивным файлам.

Обе вышеуказанные проблемы были решены путем увеличения MinBytesPerSecond, а также отключения сеансов. Я добавил подробный ответ, обобщающий каждыйбалл.

Ответы [ 4 ]

12 голосов
/ 15 марта 2012

Хотя правильным способом доставки больших файлов в IIS является следующий параметр,

  1. Установите для параметра MinBytesPerSecond значение Zero в WebLimits (это, безусловно, поможет повысить производительность, поскольку IIS выбирает закрытие клиентов, содержащих KeepAlive.соединения с меньшим размером передачи)
  2. Выделите больше рабочих процессов для пула приложений, я установил значение 8, теперь это следует делать только в том случае, если ваш сервер распространяет файлы большего размера.Это, безусловно, заставит другие сайты работать медленнее, но это обеспечит лучшую доставку.Мы установили значение 8, поскольку у этого сервера только один веб-сайт, и он просто доставляет огромные файлы.
  3. Отключить повторную обработку пула приложений
  4. Отключить сеансы
  5. Оставить буферизацию включенной
  6. Перед каждым из следующих шагов, проверьте, является ли Response.IsClientConnected верным, иначе сдайтесь и ничего не отправляйте.
  7. Установите Content-Length перед отправкой файла
  8. Сбросьте Ответ
  9. Запись в выходной поток и регулярная очистка
11 голосов
/ 07 марта 2012

Если вы установили длину содержимого с параметром bufferOutput в значение false, возможная причина сбоев заключается в том, что IIS пытается сжать отправляемый вами файл, а при установке IIS Content-Length не может изменить его обратно на сжатый. и ошибки начинаются (*).

То есть 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, то следующее, что мне пришло в голову, это то, что файл отправлен, и по какой-то причине соединение было задержано, получило тайм-аут и закрыто. Таким образом, вы получаете «Удаленный хост закрыл соединение».

Это можно решить в зависимости от причины:

  1. Клиент действительно закрыл соединение
  2. Тайм-аут взят из самой страницы, если вы используете обработчик (опять же, возможно, сообщение должно быть «Page Timed Out Out»).
  3. Тайм-аут исходит из ожидания ожидания, страница занимает больше времени выполнения, получает тайм-аут и закрывает соединение. Может быть, в этом случае сообщение было Page Timed Out.
  4. Пул перезапускается в момент отправки файла. Отключить все повторы в бассейне! Это наиболее вероятные случаи, о которых я могу подумать прямо сейчас.

Если это исходит от IIS, перейдите в свойства веб-сайта и убедитесь, что вы установили самое большое «Время ожидания подключения» и «Включить HTTP Keep-Alive».

Тайм-аут страницы путем изменения 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 время ожидания шлюза

5 голосов
/ 11 марта 2012

Я бы использовал не очень известный метод ASP.NET Response.TransmitFile , так как он очень быстрый (и, возможно, использует кеш ядра IIS) и заботится обо всех заголовках.Он основан на неуправляемом интерфейсе Windows TransmitFile API.

Но для использования этого API необходим физический файл для передачи.Итак, вот псевдо-код C #, который объясняет, как это сделать с вымышленным физическим путем к файлу 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)
2 голосов
/ 16 января 2013

Этот кусок кода работает для меня. Это немедленно запускает поток данных к клиенту. Показывает прогресс во время загрузки. Это не нарушает HTTP. Указан заголовок Content-Length, и кодирование передачи с разделением на части не используется.

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();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...