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

发布于 2024-09-13 10:34:52 字数 1364 浏览 1 评论 0原文

在 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#)�*��eVe]f@�흼��{���{��;�N'���?\fdl��J�ɞ!���?

原因是什么?

In ASP.Net MVC 2 I am using the following compression filter and, in Chrome it works fine but in Firefox 3.3.6 it returns weird characters.

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);
    }
}

Here are a sample of the characters displayed by Firefox:

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

What is the cause?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

一百个冬季 2024-09-20 10:34:52

我发现一些事情会导致您自己的压缩出现问题。

第一的。某些情况会导致响应处理方式发生彻底改变(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);
  }
}

第四。使用内容编码(而不是传输编码)时,HTTP 认为您实际上正在发送与使用不同编码不同的实体。 (传输编码认为您只是使用编码,以便使用更少的带宽,这正是我们通常真正想要的,但可惜对传输编码的支持并不常见,因此我们通过使用内容编码来代替) 。因此,您需要确保不同编码之间的电子标签(如果有)不同(在最后一个“字符之前添加 G 表示 gzip,添加 D 表示默认值应该可以解决问题,只是不要重复 mod -gzip 将其放在“字符后面的错误)。

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

第六。即使您完美地完成了所有操作,开发早期的缓存中的某些内容也可能会造成混乱,因为您刚刚在缓存后更改了缓存规则。清除缓存。

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

顺便说一句,无论客户端偏好是什么,上面的代码都支持 GZip 而不是 Deflate。如果我要忽略客户声明的偏好顺序,我会反过来做。由于 GZip 是基于 Deflate 构建的,因此 GZip 总是比 Deflate 稍大。这种差异可以忽略不计,但更重要的是,与 deflate 数据相比,某些实现将花费更多的 CPU 时间来处理 g-zip 数据,这取决于架构和软件(因此只需在一个上进行测试)机器不会告诉您足够的信息来判断这是否适用),因此对于在低端计算机上运行浏览器的客户端来说,gzip 和 deflate 之间的显着差异可能不仅仅是下载 gzip 将发送的几个额外八位字节。

A few things that I've found cause problems with rolling your own compression.

First. Some situation causes a complete change to how the response is handled (Server.Transfer, an HTTP module deferring to another HTTP module) may clear the headers, but keep the stream. Fiddler will quickly tell you if this is the case. One possibility is that this happens when you go to your error response, and an error is happening in the FF case. Forcibly decompressing the stream yourself should help diagnose here.

Conversely, a sequence of events could have led to the headers and/or the compression being doubled-up, so you end up sending a gzip of a gzip and similar. Worse yet, the filter may have been changed part-way through the response.

Third. Just putting in a DeflateStream or GZipStream as the filter does not correctly handle the case where chunked encoding is used (buffering is off, HttpResponse.Flush() is called, or a response larger than the largest allowed buffer size is sent). The following stream class handles this case correctly (it's the override of Flush() that does the fix, the extra public properties I have found useful in dealing with the cases described above).

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);
  }
}

Fourth. With a content-encoding (rather than a transfer-encoding) HTTP considers you to be actually sending a different entity than with a different encoding. (Transfer-encoding considers you to just be using the encoding so that there's less bandwidth used, which is what we normally really want, but alas support for Transfer-encoding isn't as common, so we kludge by using Content-Encoding instead). As such you need to make sure that e-tags (if there are any) are different between the different encodings (adding a G for gzip and an D for default before the last " character should do the trick, just don't repeat mod-gzip's bug of putting it after the " character).

Fifth. Related to this, you must send an appropriate Vary header given that you can vary according to content-encoding. Doing this correctly means sending Vary: Accept-Encoding, to indicate that what you send will depend on the value of that heading. Because this causes issues with IE (thankfully the next version will have some improvement, according to MS) some people send Vary: User-Agent instead (on the basis that most user-agents either accept compression content-encodings or don't, rather than requesting sometimes and not others). Note that you need to set the Vary header when you are prepared to compress, even in cases where you don't.

Sixth. Even if you're doing everything perfectly, something in the cache from earlier in your development can mess with it, as you've just changed the rules for caching after it got cached. Clear your cache.

If none of those fit the bill, at least do look at what you see in a tool like Fiddler, and what you see if you manually decompress the stream sent to FF, it should definitely help.

Incidentally, your code above favours GZip over Deflate, whatever the client preference is. If I was going to ignore client-stated preference order, I'd do it the other way around. Since GZip is built on Deflate, the GZip is always slightly larger than the Deflate. This difference is negliable, but more importantly some implementations will take much more CPU-time to work with g-zip data than deflate data, and this depends on architecture as well as software (so just testing on one machine doesn't tell you enough to judge if this applies), so for a client running their browser on a low-end machine, the appreciable difference between gzip and deflate might be more than just downloading the few extra octets gzip will send.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文