gzip/deflate failure when an exception is thrown
Asked Answered
M

3

6

I have an interesting issue with a gzip/deflate ActionFilterAttribute in ASP.NET MVC 3. If an exception is thrown by my application, instead of getting a YSOD, I get a full page of gibberish as seen below.

���I�%&/m�{J�J��t��$ؐ@�������iG#)�*��eVe]f@�흼��{����{����;�N'���?\fdl��J�ɞ!���?~|?"��Ey�')=��y6�����h����Z��2k�j���uU?�+_x-��:� �T����W�v�<[����~2�g�2���?�ʋ�y�hYՋ�������t� _N���M�l�������{��,���Xn���Q�}��������*g�������7�� ~��j'u>K�{_��IW4�>�U�w�|=-fYzR-�������|��<&�o�Z()*�S!U��k�g�������j��.����b}��ή�9X/��J�Iն��Q���z�i�n�-g٤���ݞ��Y^����H�8/��k�}]7�ǜ@�{|�g��wUd�O����죫y���o-�����ݏ��� �ZHv,�d]��١�>o3�=�3x�7MN�����������Ow���w�.o��φ�<؟M����;���vg���A>��䋟{YޟN�����Φ�$p>q����/�!�y��9�2��two������?������Ӈ���n�9�r�^����!������{���ag�?\1*c�?!�bي?���xI����u�f ?��{'�����P$�v&=#s�l�_0����΃�w�ss�����廌��⼽�r���!��{k\7M���(o������4�ߛ>�>�@"|�|v���y5�����QꆦR���JSK�&�����ߛ�p������v<�C��t��1�hOI���y{j�]i���˷���� �D'p<�$,�'M��r{-�}��CF�؛�����A��9��[�½�� �! 2�� �:��!��{�t�;�߇'y��M��+�M^#x^\����Q��jM�l��?(�]� ��IZ�ݟ[����+4#"�:�X����m�������dv>������iL�̀I |�fL�TU��ho�� �{L��_t��5�o?���h�O�UY]#�u�[���G�ޞ�=���;��8���~����d�8k�w�����yw�����ֺ��Nx��A���[��xMo��ۣf���/�Og�;y~����!

If I remove my CompressAttribute, it works as expected (I see the YSOD). So it seems that my exception handling (ElmahHandleErrorAttribute from Elmah.Contrib.Mvc) halts the remaining filters, including CompressAttribute and the response is not deflated.

Relevant code:

public sealed class CompressAttribute : ActionFilterAttribute
{
    private const string _acceptEncodingHeader = "Accept-Encoding";
    private const string _contentEncodingHeader = "Content-Encoding";

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        HttpRequestBase request = filterContext.HttpContext.Request;

        string acceptEncoding = request.Headers[_acceptEncodingHeader];

        if (String.IsNullOrEmpty(acceptEncoding))
        {
            return;
        }

        acceptEncoding = acceptEncoding.ToUpperInvariant();

        HttpResponseBase response = filterContext.HttpContext.Response;

        if (acceptEncoding.Contains("GZIP"))
        {
            response.AppendHeader(_contentEncodingHeader, "gzip");
            response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
        }
        else if (acceptEncoding.Contains("DEFLATE"))
        {
            response.AppendHeader(_contentEncodingHeader, "deflate");
            response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
        }
    }
}

Filter registration:

GlobalFilterCollection filters = GlobalFilters.Filters;
filters.Add(new ElmahHandleErrorAttribute(), 999); // Elmah.Contrib.Mvc
filters.Add(new CompressAttribute());

How can I make sure that the response is readable even when exceptions are thrown?

Mock answered 14/2, 2012 at 18:59 Comment(5)
I'm also open to other/better methods for enabling http compression.Mock
Have you looked into IIS's built in dynamic compression?Hokeypokey
@Hokeypokey I have not. Do you have a link?Mock
this and this and many more. Ask Google :)Hokeypokey
I like the idea of IIS compression since one setting can work for all sites on a server. I'll do some reading.Mock
G
7

This is because when there is an error in your application ASP.Net removes all your custom headers but the filter is still there. You could reset the filter on application error that should make the problem go away.

protected void Application_Error(object sender, EventArgs e)
{
        Response.Filter = null;
}
Germinative answered 14/2, 2012 at 19:29 Comment(0)
P
9

Here is a slightly better answer inspired by the answer from iaimtomisbehave. It lets you keep all of the code within the one class.

Add the following override to your CompressAttribute class:

public override void OnResultExecuted(ResultExecutedContext filterContext)
{
    if (filterContext.Exception != null)
    {
        filterContext.HttpContext.Response.Filter = null;
    }
}
Pedicab answered 18/2, 2012 at 15:19 Comment(2)
This is actually what I ended up doing.Mock
This works if I put it in OnActionExecuted, but not in OnResultExecuted.Bakst
G
7

This is because when there is an error in your application ASP.Net removes all your custom headers but the filter is still there. You could reset the filter on application error that should make the problem go away.

protected void Application_Error(object sender, EventArgs e)
{
        Response.Filter = null;
}
Germinative answered 14/2, 2012 at 19:29 Comment(0)
L
0

Googled the internet for a solution of the same problem and ended up here. Quite helpful answers inspired me to implement my own version mentioned below:

static public void EnableGzip()
{
    var c = HttpContext.Current;
    string a = c.Request.Headers["Accept-Encoding"];
    if (String.IsNullOrEmpty(a))
        return;
    if (!a.Contains("gzip"))
        return;
    c.Response.Filter = new GZipStream(
        c.Response.Filter, CompressionMode.Compress);
    c.Response.AppendHeader("Content-Encoding", "gzip");
    EventHandler errorHandler = null;
    errorHandler = delegate
    {
        c.Response.Filter = null;
        c.ApplicationInstance.Error -= errorHandler;
    };
    c.ApplicationInstance.Error += errorHandler;
}

Please feel free to criticize this out.

Leonaleonanie answered 30/3, 2014 at 20:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.