Firefox在ASP.Net MVC中出现压缩过滤器属性问题

Nic*_*ray 6 compression asp.net-mvc firefox

在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);
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是Firefox显示的字符示例:

I?%&/m?{J?J??t??$ @ iG#)* eVe] f @ 흼 { { ; N' ?\fdl Jɞ!?

原因是什么?

Jon*_*nna 7

我发现的一些事情导致滚动自己的压缩问题.

第一.某些情况导致完全更改响应的处理方式(Server.Transfer,一个HTTP模块延迟到另一个HTTP模块)可能会清除标头,但保留流.Fiddler会很快告诉你是否是这种情况.一种可能性是,当您转到错误响应时会发生这种情况,并且在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);
  }
}
Run Code Online (Sandbox Code Playgroud)

第四.使用内容编码(而不是传输编码),HTTP认为您实际上发送的是与不同编码不同的实体.(传输编码认为您只是使用编码,因此使用的带宽较少,这是我们通常真正想要的,但是对传输编码的支持并不常见,所以我们通过使用内容编码来改进) .因此你需要确保不同的编码之间的电子标签(如果有的话)是不同的(在最后一个"字符应该做的时候,为gzip添加一个G和默认为D,只是不要重复mod -gzip把它放在"角色"之后的错误.

第五.与此相关,您必须发送适当的Vary标头,因为您可以根据内容编码进行更改.正确执行此操作意味着发送Vary:Accept-Encoding,以指示您发送的内容将取决于该标题的值.因为这会导致IE出现问题(幸好下一版本会有一些改进,根据MS),有些人会发送Vary:User-Agent(基于大多数用户代理要么接受压缩内容编码,要么不接受比有时请求而不是其他人请求.请注意,在准备压缩时需要设置Vary标头,即使在不准备压缩的情况下也是如此.

第六.即使你正在完美地完成所有事情,你开发早期的缓存中的某些东西也会搞乱它,因为你刚刚在缓存后更改了缓存规则.清除缓存.

如果这些都不符合要求,至少要看看你在Fiddler这样的工具中看到的内容,以及如果手动解压缩发送到FF的流,你会看到它,它肯定会有所帮助.

顺便提一下,无论客户偏好如何,上面的代码都支持GZip over Deflate.如果我要忽略客户声明的偏好顺序,我会反过来做.由于GZip是基于Deflate构建的,因此GZip总是略大于Deflate.这种差异是negliable,但更重要的是一些实现将需要多少更多的CPU时间比缩小数据G-ZIP数据进行工作,这取决于架构以及软件(所以只是一台机器上测试并不能告诉你足够判断这是否适用),因此对于在低端机器上运行浏览器的客户端,gzip和deflate之间的明显差异可能不仅仅是下载gzip将发送的几个额外八位字节.