How do I enable GZIP compression for POST (upload) requests to a SOAP WebService on IIS 7?
Asked Answered
J

2

10

How can I enable compression for POSTed data uploaded to a .NET WebService (SOAP, not WCF)? I thought it would be a simple matter of enabling dynamic compression in IIS but after enabling, it's only compressing the response, not the POST request.

I've added it as a Service Reference and I can't find any settings on the generated SOAPClient to enable compression of requests.

It seems I might be missing a configuration setting or code on the client side to compress the request before sending it to the server? Or is what I'm trying to do (GZipping POST data) not even supported?

Further info: I'm using .NET 4.0 on the client and server.

Jett answered 21/5, 2013 at 13:24 Comment(1)
Please refer to this SO post (#4416677)Periphery
T
14

I've blogged on that 4 years ago

http://netpl.blogspot.com/2009/07/aspnet-webservices-two-way-response-and.html

I wonder why you haven't found this by googling. Anyway, this should work for you, we have it working in a production environment for 4 years now.

Theatricalize answered 23/5, 2013 at 18:14 Comment(12)
Actually I was already using that code but it had a couple of bugs I struggled to fix. However I've spent more time on this and have now fixed the bugs. My version fixes the following issues: Set Content-encoding gzip header when an Exception occurs on the server. Second fix: Do not attempt to decrypt GZIP request stream when request was not GZIPed. I've posted the updated version of the code here: pastebin.com/Aak9FUiwJett
Many thanks for the original solution. I will award you the bounty :)Jett
Third fix was to suppress doubling of "content-encoding" header so it became "gzip,gzip". Not sure if this is really necessary but it's not correct.Jett
Thanks Nick, I hope the solution will do a good job for you just as it does for us.Theatricalize
I've actually had to remove this code now. Even the "fixed" version has a couple of problems I don't know how to fix. Eg if the web service times out you get an error (which hides the real error) of "Object not set to an instance of an object".Jett
I remember try-catching in few places to make it more reliable and it works for us in a production environment under a heavy load for like 4 years now.Theatricalize
I've fixed one issue now, but I don't like the fact that if the operation times out, you don't get the "operation timed out" message but instead just "Object not set to an instance of an object". It makes it hard to determine what actually went wrong. Did you put in any fixes for this? If so can you post an example?Jett
Unfortunately, I don't remember any specific fixes for possible timeout.Theatricalize
Have you ever had the problem when people are using buggy proxy servers, that they get the message "the magic number in the gzip header is incorrect"? It seems the code tries to unconditionally decompress the stream, even if a proxy server has already done this, which causes an error for some users.Jett
@NickG: yes we had issues with incompatible proxies. Some issues were not resolvable.Theatricalize
I'm trying to see if it's fixable by examining the first few bytes of the stream to see if it contains the gzip magic number and if it does, append the missing "Content-encoding: gzip" header. However I'm struggling to replicate the issue on my own machine, so without being able to replicate it, I doubt I can fix it :(Jett
offtopic - not sure about link only answers - meta.stackexchange.com/questions/8231/…Phalanstery
J
7

In the end, I used Wiktor Zychla's answer but came across a couple of bugs (also mentioned in the comments of his article). For completeness, I'll post my fixed version of the HttpCompression module here but the rest of the code (and implementation guidelines) you need are in Wiktor's article.

UPDATE:

I've actually had to stop using this code as it has a bug I can't fix (even with my improved version below). For many people behind proxy servers it comes up with an error which says "the magic number in the gzip header is incorrect" and I can't figure out how to fix this. I think it's because proxy servers decompress GZIP and this code doesn't allow for receiving both zipped and non-zipped responses in its current form.

using System;
using System.IO.Compression;
using System.Web;
using System.Web.Security;

/// <summary>
/// Implement two way HTTP compression for the SOAP API
/// Code taken from: http://netpl.blogspot.co.uk/2009/07/aspnet-webservices-two-way-    response-and.html
/// Fix: Set Content-encoding: gzip header when an Exception occurs on the server.
/// Fix: Do not attempt to decrypt GZIP request stream when request was not GZIPed.
/// </summary>
public class HttpCompressionModule : IHttpModule
{
    private bool isDisposed = false;

    ~HttpCompressionModule()
    {
            Dispose(false);
    }

    public void Init(HttpApplication context)
    {
            context.BeginRequest += new EventHandler(Context_BeginRequest);
            context.PreSendRequestHeaders += new EventHandler(context_PreSendRequestHeaders);
    }

    void context_PreSendRequestHeaders(object sender, EventArgs e)
    {
            // Fix headers having been lost if an exception occurred.
            HttpApplication app = sender as HttpApplication;
            HttpContext ctx = app.Context;
            if (app.Response.Filter is GZipStream) SetEncoding("gzip");
            else if (app.Response.Filter is DeflateStream) SetEncoding("deflate");

            // Fix double header
            if (ctx.Response.Headers["Content-encoding"] == "gzip,gzip")
                    ctx.Response.Headers.Set("Content-encoding", "gzip");
    }

    public void Context_BeginRequest(object sender, EventArgs e)
    {
            HttpApplication app = sender as HttpApplication;
            HttpContext ctx = app.Context;

            // Currently only enable for the Uploader API webservice
            if (!ctx.Request.Url.PathAndQuery.ToLower().Contains("uploaderapi.asmx"))
            {
                    return;
            }

            // Add request filter if request was GZIP encoded
            string requestEncoding = ctx.Request.Headers["Content-encoding"];
            if (requestEncoding != null && requestEncoding == "gzip")
            {
               app.Request.Filter =
                    new System.IO.Compression.GZipStream(app.Request.Filter, CompressionMode.Decompress);
            }

            // Add response compression filter if the client accepts compressed responses
            if (IsEncodingAccepted("gzip"))
            {
                    app.Response.Filter = new GZipStream(app.Response.Filter, CompressionMode.Compress);
                    SetEncoding("gzip");
            }
            else if (IsEncodingAccepted("deflate"))
            {
                    app.Response.Filter = new DeflateStream(app.Response.Filter, CompressionMode.Compress);
                    SetEncoding("deflate");
            }
    }

    private bool IsEncodingAccepted(string encoding)
    {
            return HttpContext.Current.Request.Headers["Accept-encoding"] != null &&
                    HttpContext.Current.Request.Headers["Accept-encoding"].Contains(encoding);
    }

    private void SetEncoding(string encoding)
    {
            HttpContext ctx = HttpContext.Current;
            string responseEncodings = ctx.Response.Headers.Get("Content-encoding");
            if (responseEncodings == null || !responseEncodings.Contains(encoding))
                    HttpContext.Current.Response.AppendHeader("Content-encoding", encoding);
    }

    public void Dispose()
    {
            Dispose(true);
    }

    private void Dispose(bool dispose)
    {
            isDisposed = dispose;
    }
}
Jett answered 23/5, 2013 at 19:45 Comment(3)
Nick, remeber that this is not a complete answer. Both HttpResponseDecompressed and HttpRequestCompressed are also required.Theatricalize
@WiktorZychla Thanks - I've updated my answer to make it clear that your article contains all the code/instructions. My answer is only to fix those bugs / compatibility issues I found (and will be redundant if you decide to incorporate them in your article).Jett
Nick, because of your clear explanation, I am not going to make any modifications to the entry. I assume that anyone trying to make use of it will find your comments and follow your updated approach. Glad the solution helped and thanks for your fixes.Theatricalize

© 2022 - 2024 — McMap. All rights reserved.