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.