Проблемы с Firefox с атрибутом фильтра сжатия в ASP.Net MVC

В ASP.Net MVC 2 я использую следующий фильтр сжатия, а в Chrome он отлично работает, но в Firefox 3.3.6 он возвращает странные символы.

public class CompressAttribute : ActionFilterAttribute
{
 public override void OnActionExecuting(ActionExecutingContext filterContext)
 {
 //get request and response 
 var request = filterContext.HttpContext.Request;
 var response = filterContext.HttpContext.Response;
 //get requested encoding 
 if (!string.IsNullOrEmpty(request.Headers["Accept-Encoding"]))
 {
 string enc = request.Headers["Accept-Encoding"].ToUpperInvariant();
 //preferred: gzip or wildcard 
 if (enc.Contains("GZIP") || enc.Contains("*"))
 {
 response.AppendHeader("Content-encoding", "gzip");
 response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
 }
 //deflate 
 else if (enc.Contains("DEFLATE"))
 {
 response.AppendHeader("Content-encoding", "deflate");
 response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
 }
 }
 base.OnActionExecuting(filterContext);
 }
}

Вот пример символов, отображаемых Firefox:

I�%&/m�{J�J��t�� $@# Ig) * ЕВА] F @흼 {{; N"?\fdlJɞ!?

В чем причина?

1 ответ

Несколько вещей, которые я нашел, вызывают проблемы с переводом собственного сжатия.

Во-первых. Некоторая ситуация приводит к полному изменению способа обработки ответа (Server.Transfer, отсылка HTTP-модуля другому HTTP-модулю) может очистить заголовки, но сохранить поток. Скрипач быстро скажет вам, если это так. Одна из возможностей заключается в том, что это происходит, когда вы переходите к ответу на ошибку, и в случае FF происходит ошибка. Принудительно декомпрессировать поток сам должен помочь диагностировать здесь.

И наоборот, последовательность событий могла привести к тому, что заголовки и/или сжатие удвоились, поэтому вы в конечном итоге отправляете gzip gzip и тому подобное. Хуже того, фильтр, возможно, был изменен частично на ответ.

В-третьих. Просто вставьте DeflateStream или GZipStream, так как фильтр неправильно обрабатывает случай, когда используется кодирование с чередованием (буферизация отключена, вызывается HttpResponse.Flush() или отправляется ответ большего размера, чем размер наибольшего разрешенного буфера). Следующий класс потока корректно обрабатывает этот случай (это переопределение Flush(), которое делает исправление, дополнительные публичные свойства, которые я нашел полезными при рассмотрении описанных выше случаев).

public enum CompressionType
{
 Deflate,
 GZip
}
public sealed class WebCompressionFilter : Stream
{
 private readonly Stream _compSink;
 private readonly Stream _finalSink;
 public WebCompressionFilter(Stream stm, CompressionType comp)
 {
 switch(comp)
 {
 case CompressionType.Deflate:
 _compSink = new DeflateStream((_finalSink = stm), CompressionMode.Compress);
 break;
 case CompressionType.GZip:
 _compSink = new GZipStream((_finalSink = stm), CompressionMode.Compress);
 break;
 default:
 throw new ArgumentException();
 }
 }
 public Stream Sink
 {
 get
 {
 return _finalSink;
 }
 }
 public CompressionType CompressionType
 {
 get
 {
 return _compSink is DeflateStream ? CompressionType.Deflate : CompressionType.GZip;
 }
 }
 public override bool CanRead
 {
 get
 {
 return false;
 }
 }
 public override bool CanSeek
 {
 get
 {
 return false;
 }
 }
 public override bool CanWrite
 {
 get
 {
 return true;
 }
 }
 public override long Length
 {
 get
 {
 throw new NotSupportedException();
 }
 }
 public override long Position
 {
 get
 {
 throw new NotSupportedException();
 }
 set
 {
 throw new NotSupportedException();
 }
 }
 public override void Flush()
 {
 //We do not flush the compression stream. At best this does nothing, at worse it
 //loses a few bytes. We do however flush the underlying stream to send bytes down the
 //wire.
 _finalSink.Flush();
 }
 public override long Seek(long offset, SeekOrigin origin)
 {
 throw new NotSupportedException();
 }
 public override void SetLength(long value)
 {
 throw new NotSupportedException();
 }
 public override int Read(byte[] buffer, int offset, int count)
 {
 throw new NotSupportedException();
 }
 public override void Write(byte[] buffer, int offset, int count)
 {
 _compSink.Write(buffer, offset, count);
 }
 public override void WriteByte(byte value)
 {
 _compSink.WriteByte(value);
 }
 public override void Close()
 {
 _compSink.Close();
 _finalSink.Close();
 base.Close();
 }
 protected override void Dispose(bool disposing)
 {
 if(disposing)
 {
 _compSink.Dispose();
 _finalSink.Dispose();
 }
 base.Dispose(disposing);
 }
}

В-четвертых. При кодировании содержимого (а не в кодировании передачи) HTTP считает, что вы фактически отправляете другой объект, чем с другой кодировкой. (Передача-кодирование предполагает, что вы просто используете кодировку, чтобы использовать меньшую ширину полосы пропускания, чего мы обычно хотим, но, увы, поддержка Transfer-encoding не так распространена, поэтому мы клонируем, используя Content-Encoding), Таким образом, вы должны убедиться, что электронные теги (если они есть) различаются между различными кодировками (добавление G для gzip и D для значения по умолчанию до того, как последний символ должен сделать трюк, просто не повторяйте mod -gzip, помещая его после символа.

В-пятых. В связи с этим вы должны отправить соответствующий заголовок Vary, учитывая, что вы можете варьироваться в зависимости от кодировки содержимого. Это правильно означает отправку Vary: Accept-Encoding, чтобы указать, что то, что вы отправляете, будет зависеть от значения этого заголовка. Поскольку это вызывает проблемы с IE (к счастью, следующая версия будет иметь некоторые улучшения, согласно MS), некоторые люди отправляют Vary: User-Agent вместо этого (на основании того, что большинство пользовательских агентов либо принимают кодирование содержимого содержимого содержимого содержимого контента, либо, скорее, чем запрашивать иногда, а не другие). Обратите внимание, что вам нужно установить заголовок Vary, когда вы готовы сжать, даже в тех случаях, когда вы этого не делаете.

Шестое. Даже если вы все делаете отлично, что-то в кэше ранее в вашем развитии может испортиться, поскольку вы только что изменили правила кэширования после того, как они были кэшированы. Очистите кеш.

Если ни один из них не соответствует законопроекту, по крайней мере, посмотрите на то, что вы видите в инструменте, таком как Fiddler, и что вы видите, если вы вручную распаковываете поток, отправленный в FF, это определенно поможет.

Кстати, ваш код выше способствует GZip над Deflate, независимо от предпочтений клиента. Если бы я собирался проигнорировать заказ предпочтений клиента, я бы сделал это наоборот. Поскольку GZip построен на Deflate, GZip всегда немного больше, чем Deflate. Это различие несущественно, но, что более важно, некоторым реализациям потребуется гораздо больше времени процессора для работы с данными g-zip, чем с дефляцией данных, и это зависит от архитектуры и программного обеспечения (поэтому просто тестирование на одной машине не говорит вам достаточно судить, применимо ли это), поэтому для клиента, запускающего свой браузер на машине низкого класса, заметная разница между gzip и deflate может быть больше, чем просто загрузка нескольких дополнительных октетов, которые отправит gzip.

licensed under cc by-sa 3.0 with attribution.