Почему ASP.NET заменяет заголовок Content-Length заголовком Transfer-Encoding при ручной очистке ответа?

В нашем веб-приложении (ASP.NET Web Forms) есть страница, которая отображает пользователям недавно созданный PDF-файл. Поскольку PDF-файл иногда бывает довольно большим, мы применили "потоковый" подход, чтобы отправлять его в браузер клиента частями.

Несмотря на то, что данные передаются частями, мы знаем полный размер файла до его отправки, поэтому мы устанавливаем заголовок Content-Length соответствующим образом. Это работало в нашей производственной среде в течение некоторого времени (и продолжает работать в нашей тестовой среде с практически идентичной конфигурацией) до сегодняшнего дня. Проблема заключалась в том, что Chrome пытался открыть PDF-файл, но зависал на анимации "Loading".

Поскольку в нашей тестовой среде все по-прежнему работало нормально, я смог использовать Firebug, чтобы посмотреть на заголовки ответов, которые возвращались в обеих средах. В тестовой среде я видел правильный заголовок 'Content-Length', а в производственной он был заменен на Transfer-Encoding: chunked. Chrome это не нравится, отсюда и зависание.

Я читал некоторые статьи и сообщения о том, что заголовок Transfer-Encoding может отображаться, когда не указан заголовок Content-Length, но мы указываем заголовок Content-Length, и все по-прежнему работает при выполнении того же кода для того же PDF-файла на тестовом сервере.

Оба сервера - тестовый и рабочий - работают под управлением IIS 7.5, и на обоих включено динамическое и статическое сжатие.

Вот код, о котором идет речь:

var fileInfo = new FileInfo(fileToSendDown);
Response.ClearHeaders();
Response.ContentType = "application/pdf";            
Response.AddHeader("Content-Disposition", "filename=test.pdf");
Response.AddHeader("Content-Length", fileInfo.Length.ToString());
var buffer = new byte[1024];
using (var fs = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    int read;
    while ((read = fs.Read(buffer, 0, 1024)) > 0)
    {
        if (!response.IsClientConnected) break;
        Response.OutputStream.Write(buffer, 0, read);
        Response.Flush();
    }
}

Мне посчастливилось наблюдать такое же поведение на моей локальной рабочей станции, поэтому, используя отладчик, я смог увидеть, что заголовок 'Transfer-Encoding: chunked' устанавливается на втором проходе через цикл while во время вызова 'Flush'. В этот момент ответ имеет и заголовок Content-Length, и заголовок Transfer-Encoding, но почему-то к тому моменту, когда ответ достигает браузера, Firebug показывает только заголовок Transfer-Encoding.

UPDATE

Я думаю, что отследил это до использования комбинации отправки данных "кусками" и присоединения 'Filter' к объекту HttpResponse (мы использовали фильтр для отслеживания размера viewstate, отправляемого на каждую страницу). Нам нет смысла использовать HTTP-фильтр при отправке PDF в браузер, поэтому удаление фильтра здесь решило нашу проблему. Я решил копнуть немного глубже чисто из любопытства и обновил этот вопрос, чтобы кто-нибудь еще столкнулся с этой проблемой в будущем.

У меня есть простое приложение на AppHarbor, которое воспроизводит проблему: http://transferencodingtest.apphb.com/. Если вы отметите оба поля 'Use Filter?' и 'Send In Chunks?', вы должны увидеть, что заголовок 'transfer-encoding: chunked' отображается (используя инструменты разработчика Chrome, Firebug, Fiddler, что угодно). Если ни одно из этих полей не отмечено, вы получите правильный заголовок content-length. Основной код размещен на github, так что вы можете посмотреть, что происходит за кулисами:

https://github.com/appakz/TransferEncodingTest

Обратите внимание, что для локального воспроизведения вам нужно установить локальный сайт в IIS 7.5 (7 тоже может работать, я не пробовал). Сервер разработки ASP .NET, поставляемый с Visual Studio, НЕ воспроизводит проблему.

Я добавил некоторые подробности в сообщение в блоге здесь: 'Content-Length' Header Replaced With 'Transfer-Encoding: Chunked' в ASP .NET

17
задан Jesse Taber 14 February 2015 в 02:31
поделиться