HTTP compression in the .NET Framework 1.1






4.57/5 (16 votes)
An article on HTTP compression in the .NET Framework 1.1.
Introduction
This article will show how to request for HTTP compression and handle a compressed response from a web server (or any appliance connected to the server) without changing the client application.
The factory pattern of creating WebRequest instances
The WebRequest
supplies two methods to create instances:
CreateDefault
- Initializes a newWebRequest
instance for the specified URI scheme.Create
- Initializes a newWebRequest
instance.
The WebRequest
class uses instances of classes that implement the IWebRequestCreate
interface and are registered in the webRequestModules
section in the configuration files. The Create
method (called by both CreateDefault
and Create
) returns an initialized instance of a WebRequest
descendent class capable of performing a standard request/response transaction for the protocol without needing any protocol-specific fields modified.
Because we can control the WebRequest
factory using the configuration files, we can change the behavior of already built applications (our applications, or even the .NET Framework) to request and handle HTTP compression without changing them.
The WebRequest
descendent instances will be responsible to create the WebResponse
descendent instance that will handle the response from the web server.
The code
As shown before, to add HTTP compression to our applications, we just have to build three classes:
CompressibleHttpRequestCreator
- implementing theIWebRequestCreate
interface.CompressibleHttpWebRequest
- derived fromWebRequest
.CompressibleHttpWebResponse
- derived fromWebResponse
.In order for the applications that use
HttpWebRequest
andHttpWebResponse
to work without any changes,CompressibleHttpWebRequest
has to derive fromHttpWebRequest
, andCompressibleHttpWebResponse
has to derive fromHttpWebResponse
.Fortunately,
HttpWebRequest
andHttpWebResponse
are notsealed
(NotInheritable
in VB.NET). Unfortunately, these classes don't have anypublic
constructor, and the onlyprotected
constructor each one has is the one required by the implementation of theISerializable
interface. Due to this drawback, reflection must be used.CompressibleHttpRequestCreator
This is the class that will be implementing the
IWebRequestCreate
interface and is used to createHttpWebRequest
descendant instances.As mentioned before, the only way to create an instance of a
HttpWebRequest
derived class, is by deserialization. To accomplish this, an instance ofHttpWebRequest
will be created, serialized, and deserialized into an instance ofCompressibleHttpWebRequest
.public class CompressibleHttpRequestCreator : IWebRequestCreate { WebRequest IWebRequestCreate.Create(Uri uri) { ISerializable httpWebRequest = Activator.CreateInstance( typeof(HttpWebRequest), BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { uri }, null) as ISerializable; if (httpWebRequest == null) { return null; } SerializationInfo serializationInfo = new SerializationInfo( typeof(CompressibleHttpWebRequest), new FormatterConverter()); StreamingContext streamingContext = new StreamingContext(StreamingContextStates.All); httpWebRequest.GetObjectData(serializationInfo, streamingContext); CompressibleHttpWebRequest webRequest = new CompressibleHttpWebRequest(serializationInfo, streamingContext); webRequest.Method = serializationInfo.GetString("_OriginVerb"); return webRequest; } }
CompressibleHttpWebRequest
This class will handle the setup of the HTTP request and the creation of the
HttpWebResponse
derived instance to handle the response.As with
CompressibleHttpWebRequest
, when creating theHttpWebResponse
, an instance ofHttpWebResponse
will be created, serialized, and deserialized into an instance ofCompressibleHttpWebResponse
.To specify what types of compression will be accepted in the response, the
AcceptEncodings
will be used. Its value will, also, be used to set theaccept-encoding
HTTP header in the request (in theBeginGetResponse
). The possible values forAcceptEncodings
are:Identity
- The default (identity) encoding; the use of no transformation whatsoever.GZip
- An encoding format produced by the file compression program "gzip" (GNU zip) as described in RFC 1952 [25]. This format is a Lempel-Ziv coding (LZ77) with a 32 bit CRC.Deflate
- The "zlib" format defined in RFC 1950 [31] in combination with the "deflate" compression mechanism described in RFC 1951 [29].
[Serializable] public class CompressibleHttpWebRequest : HttpWebRequest { private static FieldInfo m_UsesProxySemanticsFieldInfo = typeof(HttpWebResponse).GetField("m_UsesProxySemantics", BindingFlags.NonPublic | BindingFlags.Instance); private static FieldInfo m_ConnectStreamFieldInfo = typeof(HttpWebResponse).GetField("m_ConnectStream", BindingFlags.NonPublic | BindingFlags.Instance); private AcceptEncodings acceptEncodings = AcceptEncodings.GZip | AcceptEncodings.Deflate | AcceptEncodings.Identity; public AcceptEncodings AcceptEncodings { get { return this.acceptEncodings; } set { this.acceptEncodings = value; } } protected internal CompressibleHttpWebRequest(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { } public override IAsyncResult BeginGetResponse(AsyncCallback callback, object state) { if (this.acceptEncodings != AcceptEncodings.Identity) { this.Headers.Add("Accept-Encoding", (this.acceptEncodings & ~AcceptEncodings.Identity).ToString().ToLower()); } return base.BeginGetResponse(callback, state); } public override WebResponse EndGetResponse(IAsyncResult asyncResult) { ISerializable httpWebResponse = base.EndGetResponse(asyncResult) as ISerializable; if (httpWebResponse == null) { return null; } SerializationInfo serializationInfo = new SerializationInfo(typeof(CompressibleHttpWebResponse), new FormatterConverter()); StreamingContext streamingContext = new StreamingContext(StreamingContextStates.All); httpWebResponse.GetObjectData(serializationInfo, streamingContext); CompressibleHttpWebResponse webResponse = new CompressibleHttpWebResponse(serializationInfo, streamingContext); m_UsesProxySemanticsFieldInfo.SetValue(webResponse, m_UsesProxySemanticsFieldInfo.GetValue(httpWebResponse)); m_ConnectStreamFieldInfo.SetValue(webResponse, m_ConnectStreamFieldInfo.GetValue(httpWebResponse)); return webResponse; } }
CompressibleHttpWebResponse
This is the easiest one. All that's needed is to handle the response stream accordingly to the
content-encoding
HTTP response header. Unfortunately, the .NET Framework 1.1 base classes don't provide any class for handling Deflate and GZip streams. For that, the SharpZipLib will be used.[Serializable] public class CompressibleHttpWebResponse : HttpWebResponse { public CompressibleHttpWebResponse(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { } public override Stream GetResponseStream() { Stream stream = base.GetResponseStream(); if (stream == null) { return Stream.Null; } if (string.Compare(ContentEncoding, "gzip", true) == 0) { return new GZipInputStream(stream); } else if (string.Compare(ContentEncoding, "deflate", true) == 0) { return new InflaterInputStream(stream); } else { return stream; } } }
Configuration
Now, to add HTTP compression support to any application, all that's needed is to add the corresponding entry to the
webRequestModules
section in the configuration file.<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.net> <webRequestModules> <add prefix="http" type="PaJoCoMo.Net.CompressibleHttpRequestCreator, PaJoCoMo" /> </webRequestModules> </system.net> </configuration>