“内容编码”如果发生异常,标头将从 HttpHandler 响应中消失

发布于 2024-12-26 17:47:41 字数 1749 浏览 2 评论 0原文

我有一个自定义的 HttpHandler,在其中手动启用输出压缩,如下所示:

context.Response.AppendHeader("Content-encoding", "gzip");
context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);

这对于大多数请求来说效果很好,但是当遇到异常时,“Content-encoding”标头会从响应中消失,而压缩过滤器仍保留在原位。结果是错误页面是 gzip 压缩的,但浏览器没有收到表明这一事实的标头。然后,浏览器尝试将仍然压缩的数据显示为文本,即 gobbledygook

完整的测试用例代码如下所示。尝试交替禁用压缩或不抛出异常。

任何人都可以解释为什么“内容编码”标头消失吗?

我想我可以简单地启用压缩作为处理程序执行的最后一件事,这样,如果遇到异常,它永远不会到达添加压缩过滤器的点;但我所看到的行为让我觉得这是一个错误。有人能证实吗?

public class TestHandler : IHttpHandler 
{
    public void ProcessRequest(HttpContext context)
    {
        CompressResponse(context);
        context.Response.Write("Hello world");

        // Throw an exception for testing purposes
        throw new Exception("Just testing...");
    }

    private void CompressResponse(HttpContext context)
    {
        string acceptEncoding = context.Request.Headers["Accept-Encoding"];
        if (String.IsNullOrEmpty(acceptEncoding))
        {
            return;
        }

        // gzip or wildcard
        if (acceptEncoding.ToLower().Contains("gzip") || acceptEncoding.Contains("*"))
        {
            context.Response.AppendHeader("Content-encoding", "gzip");
            context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);
            return;
        }

        // Also handles deflate (not shown here)
        // <snip>
    }

    public bool IsReusable
    {
        get { return true; }
    }
}

编辑:我在测试用例中看到的仍然编码的响应的屏幕截图: https://i.sstatic .net/R3Wmq.png

I have a custom HttpHandler in which I manually enable output compression, like so:

context.Response.AppendHeader("Content-encoding", "gzip");
context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);

This works nicely for most requests, but when an exception is encountered the "Content-encoding" header disappears from the response, while the compression filter remains in place. The result is that the error page is gzip compressed, but the browser receives no header indicating that fact. The browser then tries to display the still-compressed data as text, which is gobbledygook.

Full test case code is shown below. Try alternately disabling the compression or not throwing the exception.

Can anyone shed some light on why the "Content-encoding" header disappears?

I suppose I could simply enable compression as the last thing the handler does, so that if an exception is encountered it never reaches the point where the compression filter is added; but the behavior I'm seeing strikes me as a bug. Can anyone confirm?

public class TestHandler : IHttpHandler 
{
    public void ProcessRequest(HttpContext context)
    {
        CompressResponse(context);
        context.Response.Write("Hello world");

        // Throw an exception for testing purposes
        throw new Exception("Just testing...");
    }

    private void CompressResponse(HttpContext context)
    {
        string acceptEncoding = context.Request.Headers["Accept-Encoding"];
        if (String.IsNullOrEmpty(acceptEncoding))
        {
            return;
        }

        // gzip or wildcard
        if (acceptEncoding.ToLower().Contains("gzip") || acceptEncoding.Contains("*"))
        {
            context.Response.AppendHeader("Content-encoding", "gzip");
            context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);
            return;
        }

        // Also handles deflate (not shown here)
        // <snip>
    }

    public bool IsReusable
    {
        get { return true; }
    }
}

EDIT: Screenshot of the still-encoded response I'm seeing with my test case: https://i.sstatic.net/R3Wmq.png

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

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

发布评论

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

评论(4

深巷少女 2025-01-02 17:47:41

我也遇到了这个问题。追踪起来很复杂。我不确定整个情况的具体细节,但我认为发生的是内存泄漏。

当您第一次执行此操作时:

context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);

您正在将非托管资源分配给过滤器。通常,这将被包装在 using 语句中,以便在出现任何问题时能够正确处理它。

因此,当出现问题时,就会出现问题。 Filter 包含一个流,即使响应被写入时出现黄屏死机,该流仍处于打开状态。随之而来的是疯狂(如屏幕截图所示)。

不要害怕!实际上有一个简单的方法可以解决这个问题。丢弃过滤器。幸运的是,已经有一个地方可以应用这种过滤器处置的全局检查。

global.asax.cs

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
        filters.Add(new HandleErrorAttribute());//default handler
        filters.Add(new HandleErrorEncodingAttribute());//extra check for filter disposal
}

错误处理程序命名空间

public class HandleErrorEncodingAttribute : FilterAttribute, IExceptionFilter
{
    public virtual void OnException(ExceptionContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }
        if (filterContext.IsChildAction)
        {
            return;
        }
        // If custom errors are disabled, we need to let the normal ASP.NET exception handler
        // execute so that the user can see useful debugging information.
        if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
        {
            filterContext.HttpContext.Response.Filter.Dispose();//fixes response stream
            return;
        }
    }
}

I encountered this problem as well. It was complicated to track down. I am unsure of the exact specifics of this whole situation, but what I think happens is that there is a memory leak.

When you first do this:

context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);

you are assigning an unmanaged resource to Filter. Normally this would be wrapped in a using statement so that it would be properly disposed in case anything went wrong.

So, when something does go wrong, there is a problem. The Filter contains a stream which is still open even though the response is being written to with the yellow screen of death. What ensues is madness (as shown in your screen shot).

Fear not! There is actually an easy way to overcome this issue. Dispose of the filter. Luckily, there is already a place to apply this global check for filter disposal.

global.asax.cs

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
        filters.Add(new HandleErrorAttribute());//default handler
        filters.Add(new HandleErrorEncodingAttribute());//extra check for filter disposal
}

error handler namespace

public class HandleErrorEncodingAttribute : FilterAttribute, IExceptionFilter
{
    public virtual void OnException(ExceptionContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }
        if (filterContext.IsChildAction)
        {
            return;
        }
        // If custom errors are disabled, we need to let the normal ASP.NET exception handler
        // execute so that the user can see useful debugging information.
        if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
        {
            filterContext.HttpContext.Response.Filter.Dispose();//fixes response stream
            return;
        }
    }
}
川水往事 2025-01-02 17:47:41

如果出现异常,那么服务器将刷新当前设置的标头和内容,因为它们是错误的,就像您在出现异常之后所做的那样。

至少,很明显,您要发送的 200 状态(因为所有不更改状态的成功响应都会发送 200,并且在出现未处理的异常时,它不再成功)是错误的,但其他所有相关内容你想做但未能实现的事情,所以一切都是错误的,一切都会过去。

但过滤器不会重置。

如果合适,请重置错误页面中的标题,或者不要设置过滤器,除非您可以确定一切都已准备好刷新。我会选择前者,没有理由不能压缩错误页面。

如果您调用了Flush(),则无法发送标头,因为好吧,因为您已经刷新了。标题要去哪里?

If you have an exception, then the server will flush the currently set headers and content, because they're wrong, as you did after all have an exeption.

At the very least, it's clear that the 200 status you were going to send (because all successful responses that don't change the status send a 200, and upon an unhandled exception it was no longer succesful) is wrong, but everything else related to something you were going to do but failed to achieve, so it's all wrong and it all goes.

Filters aren't reset though.

Either reset the headers in the error page if appropriate, or don't set the filter unless you can be sure that everything is ready to flush. I'd go for the former, no reason why error pages can't be compressed too.

You can't send a header if you've called Flush(), because well, because you've flushed. Where's the header going to go?

电影里的梦 2025-01-02 17:47:41

当我在 WebForms 应用程序上强制使用 gzip 时,也发生了同样的事情。为了修复它,我必须清除 Global.asax.cs 中 Application_Error 方法中的过滤器。

protected void Application_Error(Object sender, EventArgs e)
{
    Response.Filter = null;
}

发生这种情况的原因是 b/c 在应用程序出现错误之前设置了过滤器。由于某种原因,黄屏错误消息会清除内容编码标头,但不会对响应过滤器执行任何操作。

I had the same thing happen when forcing gzip on a WebForms application. In order to fix it I had to clear the filter in the Application_Error method in Global.asax.cs

protected void Application_Error(Object sender, EventArgs e)
{
    Response.Filter = null;
}

The reason this is happening is b/c the filter is being set before the app has an error. And for some reason the yellow screen error message clears the Content-encoding header but doesn't do anything to the response filter.

坏尐絯℡ 2025-01-02 17:47:41

我测试了你的代码,没有发现任何问题。是的,gzip 没有设置,但是过滤器也没有设置,asp 获得了控制权并发送错误。

强制标头刷新会产生真正的问题

 CompressResponse(context);
 context.Response.Flush(); 

如果我强制 gzip 标头,则页面无法正确呈现。

两个人认为这可能是你的问题。您没有设置页面编码

context.Response.ContentEncoding = new UTF8Encoding();

,也没有设置 ContentType

context.Response.ContentType = "text/plain";

也许这就是您无法正确渲染页面的原因。然而,在我的测试中,即使如此,您描述的问题也不会出现。

I test your code and I can not find any issue. Yes the gzip is not set, but the filter also not have been set and asp gets the control and send an error.

Forcing the header to flush make a real problem

 CompressResponse(context);
 context.Response.Flush(); 

If I force the gzip header then the page is not render correctly.

Two thinks here maybe is your issue. You do not have set the page encoding

context.Response.ContentEncoding = new UTF8Encoding();

and you do not have set ContentType

context.Response.ContentType = "text/plain";

Maybe some of this is the reason that you get not corrected page render. How ever in my tests even with that the issue you describe not appears.

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