Este artigo descreve a implementação de classes utilitárias que permitirão às requisições de HTTP informar ao Server (ou a qualquer aplicação na rede entre o cliente e o Server), que tipos de compressão poderão manipular, e descompactar a resposta do Server (caso exista), sem alterar a aplicação cliente. A compressão HTTP é muito útil quando o custo da conexão for alto. O factory pattern de criar instâncias WebRequest. O WebRequest provê dois métodos para criar instâncias:

  • CreateDefault - Inicializa uma nova instância WebRequest para o esquema de URI especificado.
  • Create - Inicializa nova instância WebRequest para o esquema de URI especificado.

A classe WebRequest usa instâncias de classes que implementam a conexão IWebRequestCreate e são registradas na seção webRequestModules dos arquivos de configuração. O método Create (chamado por CreateDefault e Create) retorna uma instância inicializada de uma classe WebRequest descendente, capaz de executar uma transação request/response padrão para o protocolo, sem que qualquer campo específico do mesmo seja modificado.

Por outro lado, as instâncias derivadas do WebRequest previamente criado, retornarão instâncias derivadas WebResponse que tratarão a resposta de HTTP.

Por causa do modo como é implementado o factory pattern, podemos mudar o comportamento das aplicações já existentes (nossas aplicações ou mesmo as do .NET Framework), para solicitar e manipular a compressão HTTP, alterando apenas os arquivos de configuração.

O código

Como mostrado antes, para acrescentar compressão HTTP às nossas aplicações, temos apenas que construir três classes:

  • CompressibleHttpRequestCreator – que implementa a interface IWebRequestCreate.
  • CompressibleHttpWebRequest - derivada de WebRequest.
  • CompressibleHttpWebResponse - derivada de WebResponse.

Para que as aplicações que usam o HttpWebRequest e o HttpWebResponse possam funcionar sem qualquer alteração, o CompressibleHttpWebRequest tem que derivar de HttpWebRequest e o CompressibleHttpWebResponse tem que derivar de HttpWebResponse.

Felizmente, HttpWebRequest e HttpWebResponse não são “sealed” (NotInheritable em VB.NET). Infelizmente, estas classes não têm nenhum constructor público e o único constructor protected (Protected em VB.NET) que tem, é o único requerido pela implementação da interface ISerializable. Devido a esta desvantagem, devemos utilizar reflexão.

CompressibleHttpRequestCreator

Esta é a classe que implementará a interface do IWebRequestCreate e é utilizada para criar instâncias descendentes de WebRequest.

Como mencionado antes, o único modo de criarmos uma instância de uma classe HttpWebRequest derivada, é via deserialização. Para tanto, criaremos uma instância de HttpWebRequest, a qual será serializada e desserializeda em uma instância de CompressibleHttpWebRequest. Para garantir consistência, serão copiados todos os valores de campos da instância HttpWebRequest anteriormente criada, para a nova instância CompressibleHttpWebRequest.


public class CompressibleHttpRequestCreator: IWebRequestCreate

{
    public CompressibleHttpRequestCreator()

    {

    }

    WebRequest IWebRequestCreate.Create(Uri uri)

    {

        ISerializable httpWebRequest =

            Activator.CreateInstance(typeof(HttpWebRequest),

            BindingFlags.CreateInstance | BindingFlags.Public |

            BindingFlags.NonPublic | BindingFlags.Instance,

            null, new object[] { uri, null }, 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 compressibleHttpRequest = new

            CompressibleHttpWebRequest(serializationInfo, streamingContext);

        Utils.FieldCopy(httpWebRequest, compressibleHttpRequest);

 
        return compressibleHttpRequest;

    }

}

CompressibleHttpWebRequest

Esta classe manipulará a preparação (setup) da requisição de HTTP e a criação da Instância derivada HttpWebResponse para manipular a resposta. Para especificar quais tipos de compressão serão aceitos na resposta, será utilizado o AcceptEncodings. Seu valor será também utilizado para configurar o cabeçalho accept-encoding (accept-encoding header) de HTTP na requisição (antes da chamada de HttpWebRequest.BeginGetResponse e de HttpWebRequest.GetResponse). Os possíveis valores para AcceptEncodings são:

  • Identity - a codificação padrão (identity); não necessita de nenhuma transformação.
  • GZip - um formato de codificação produzido pelo programa de compressão de arquivos “gzip" (GNU zip) como descrito em RFC 1952 [25]. Este formato é um código Lempel-Ziv (LZ77) com um CRC de 32 bits.
  • Deflate – o formato "zlib" definido em RFC 1950 [31] em combinação com o mecanismo de compressão “deflate" descrito em RFC 1951 [29].

Como acontece com o CompressibleHttpWebRequest, ao criarmos a instância CompressibleHttpWebResponse, uma instância HttpWebResponse será criada, serializada e deserializada em uma instância CompressibleHttpWebResponse (depois da chamada para HttpWebRequest.EndGetResponse, e HttpWebRequest.GetResponse).


[ISerializable]

public class CompressibleHttpWebRequest : HttpWebRequest

{

    private AcceptEncodings acceptEncodings = AcceptEncodings.GZip |

                 AcceptEncodings.Deflate | AcceptEncodings.Identity;

 
    protected internal CompressibleHttpWebRequest(SerializationInfo

        serializationInfo, StreamingContext streamingContext)

        : base(serializationInfo, streamingContext)

    {

    }

    public AcceptEncodings AcceptEncodings

    {
        get

        {
            return this.acceptEncodings;
        }

        set

        {

            this.acceptEncodings = value;

        }

    }

    public override IAsyncResult

           BeginGetResponse(AsyncCallback callback, object state)

    {

        BeforeGetResponse();
        return base.BeginGetResponse(callback, state);

    }

    public override WebResponse GetResponse()

    {

        BeforeGetResponse();

        return AfterGetResponse(base.GetResponse() as ISerializable);

    }

    public override WebResponse EndGetResponse(IAsyncResult asyncResult)

    {

        return AfterGetResponse(base.EndGetResponse(asyncResult)

                                              as ISerializable);

    }

    private void BeforeGetResponse()

    {

        if (this.acceptEncodings != AcceptEncodings.Identity)

        {

            this.Headers.Add("Accept-Encoding",

                 (this.acceptEncodings &

                 ~AcceptEncodings.Identity).ToString().ToLower());

        }

    }

 
    private WebResponse AfterGetResponse(ISerializable httpWebResponse)

    {

        if (httpWebResponse == null)

        {

            return null;

        }

        SerializationInfo serializationInfo = new

           SerializationInfo(typeof(CompressibleHttpWebResponse),

           new FormatterConverter());

        StreamingContext streamingContext = new

           StreamingContext(StreamingContextStates.All);

        httpWebResponse.GetObjectData(serializationInfo, streamingContext);

 
        CompressibleHttpWebResponse compressibleHttpWebResponse = new

           CompressibleHttpWebResponse(serializationInfo, streamingContext);

        Utils.FieldCopy(httpWebResponse, compressibleHttpWebResponse);


        return compressibleHttpWebResponse;

    }

}

CompressibleHttpWebResponse

Esta é a mais fácil. Tudo que é preciso fazer, é manipular o fluxo de resposta de acordo com o cabeçalho de resposta content-encoding http (content-encoding HTTP response header). Felizmente, o .NET Framework 2.0 provê classes para manipular streams Deflate e GZip, no System.IO.Compression, namespace.

  • GZip - System.IO.Compression.GZipStream.
  • Deflate - System.IO.Compression.DeflateStream.
  • Qualquer outro - nenhuma descompressão será realizada.
 
[ISerializable]

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 System.IO.Compression.GZipStream(stream,

                                   CompressionMode.Decompress);

        }

        else if (string.Compare(ContentEncoding,

                          "deflate", true) == 0)

        {

            return new System.IO.Compression.DeflateStream(stream,

                                      CompressionMode.Decompress);

        }

        else

        {

            return stream;

        }

    }

}

Configuração

Agora, para acrescentar suporte de compressão HTTP a qualquer aplicação, tudo que é necessário fazer é acrescentar as entradas correspondentes à seção webRequestModules derivadas do arquivo configuração.