Desenvolvimento - ASP. NET

.NET Framework 2.0: Compressão de Arquivos no .NET

Nas versões 1.X do .NET não tínhamos nenhuma classe que pudesse nos disponibilizar recursos para comprimir e/ou descomprimir arquivos, o que nos forçava a buscar em outros componentes de terceiros.

por Israel Aéce



Nas versões 1.X do .NET não tínhamos nenhuma classe que pudesse nos disponibilizar recursos para comprimir e/ou descomprimir arquivos, o que nos forçava a buscar em outros componentes, de terceiros, esta funcionalidade se precisássemos disso em nossa aplicação, seja ela qual for.

Já agora nesta nova versão do .NET Framework, 2.0, temos um novo namespace, dentro do System.IO, chamado System.IO.Compression. Este namespace contém 2 classes chamadas DeflateStream e GZipStream. Elas tem uma interface semelhante, mas uma delas, a DeflateStream utiliza o algoritmo Deflate para comprimir e descomprimir os arquivos. Temos também um enumerador dentro deste namespace chamado CompressionMode, que indica o que será feito (Compressão ou Descompressão) com o Stream.

Para ilustrarmos isso, vamos fazer um teste utilizando a classe GZipStream, comprimindo um arquivo de 527 KB, analisando os códigos abaixo:

1
2
Imports System.IO
Imports System.IO.Compression

Código 1 - Importando os Namespaces necessários.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
Public Sub Comprime(ByVal arquivo As String)
Dim stream As FileStream = Nothing
Try
stream = New FileStream(arquivo, FileMode.Open, FileAccess.Read, FileShare.Read)
Dim buffer(stream.Length - 1) As Byte
Dim count As Integer = stream.Read(buffer, 0, buffer.Length)
stream.Close()
Dim ms As New MemoryStream()
Dim zip As New GZipStream(ms, CompressionMode.Compress, True)
zip.Write(buffer, 0, buffer.Length)
zip.Close()
WriteFile("ArquivoCompact.gz", ms.ToArray, ms.Length)
Finally
If Not (stream Is Nothing) Then
stream.Close()
End If
End Try
End Sub
Public Sub Descomprime(ByVal arquivo As String)
Dim stream As FileStream = Nothing
Try
stream = New FileStream(arquivo, FileMode.Open, FileAccess.Read, FileShare.Read)
Dim ms As New MemoryStream(stream.Length)
Dim unzip As New GZipStream(ms, CompressionMode.Decompress, True)
Dim data(stream.Length - 1) As Byte
stream.Read(data, 0, data.Length)
ms.Write(data, 0, data.Length)
ms.Position = 0
Dim dataOutput() As Byte = ReadBytes(unzip)
WriteFile("ArquivoOriginal.exe", dataOutput, dataOutput.Length)
unzip.Close()
Finally
If Not (stream Is Nothing) Then
stream.Close()
End If
End Try
End Sub
Public Function ReadBytes(ByVal s As Stream) As Byte()
Dim buffer(10000) As Byte
Dim bytesLidos As Integer = 0
Using ms As New MemoryStream()
Do
bytesLidos = s.Read(buffer, 0, 10000)
ms.Write(buffer, 0, bytesLidos)
Loop Until bytesLidos
Return ms.ToArray()
End Using
End Function

Código 2 - Função que comprime de descomprime o arquivo informado.


Como podemos ver no código 2 logo acima, utilizamos a classe GZipStream tanto para comprimir quanto para descomprimir o arquivo do nosso exemplo. Para o nome do arquivo que é passado para a função "Comprime", resgatamos o mesmo para um FileStream. Com isso, conseguimos manipulá-lo e passarmos para a classe de compressão fazer o trabalho e gerar o arquivo em menor tamanho. Com um objeto do tipo MemoryStream, recuperamos o conteúdo que é gerado pelo GZipStream e assim salvamos/criamos o arquivo com extensão "*.gz", já comprimido.

O Método WriteFile tem apenas a finalidade de gerar os arquivos fisicamente no disco, dado um nome do arquivo e o conteúdo que será salvo para este. Abaixo o código deste método para análise:

1
2
3
4
5
6
7
8
Public Sub WriteFile(ByVal fileName As String, _
ByVal data() As Byte, _
ByVal count As Integer)
Dim f As New FileStream(fileName, FileMode.Create, FileAccess.Write)
f.Write(data, 0, count)
f.Close()
End Sub

Código 3 - Método auxiliar que gera os arquivos fisicamente.


Agora já analisando a outra parte do código 2, ou melhor, o método "Descomprime", ele faz basicamente da mesma forma que anteriormente, ou seja, carrega o arquivo comprimido para um FileStream e depois passa a instância de um MemoryStream para a classe fazer o trabalho de descompressão do arquivo informado. Um método auxiliar, chamado "ReadBytes", é criado para recuperarmos o conteúdo extraído/original do arquivo através do GZipStream.

Depois de executar o código, o arquivo EXE de 527 KB, diminuiu para 225 KB, e com certeza é bem considerável.

Utilizando a Compreensão no ASP.NET

Mas claro que a única utilidade deste namespace não é somente para comprimir e descomprimir arquivos em disco. No ASP.NET, ele terá um papel bastante importante, ou seja, poderemos através dele, comprimirmos o conteúdo/output de páginas ASPX antes de enviar ao cliente (clientes que suportam este tipo de compressão), para termos uma melhor performance e consequentemente diminuimos o tráfego de dados na rede.

Para aqueles que utilizam IIS 6.0, pode ir até as propriedades da aplicação, na Tab "Service" e marcar a compressão de arquivos, já que a mesma, por padrão, vem desabilitada e assim não seria necessário nenhuma espécie de programação, pois o IIS cuidaria disso.

Mas há casos em que não temos o IIS 6.0 ou o acesso a ele. Isso ocorre quando temos a nossa aplicação hospedada em um host qualquer, e em quase 100% dos casos, o cliente não tem acesso ao IIS. Nesta situação é que o ASP.NET 2.0 + .NET Framework 2.0 nos ajudam, pois podemos comprimir os "arquivos" programaticamente através de HttpModules e fazendo também o uso das classes de compressão que vimos no início do artigo.

Chamo a atenção aqui que é extremamente necessário que o cliente tenha habilitado no seu browser o suporte ao HTTP 1.1, pois a compressão é uma "feature" disponibilizada nesta versão. Caso ela não esteja configurada, então o processo não funcionará. Inclusive você poderá identificar isso programaticamente no próprio ASP.NET, ou mesmo, através do Trace.

Para habilitar o suporte ao HTTP 1.1 no seu browser (Internet Explorer), que por padrão já vem habilitado, deve-se ir até o menu Tools (Ferramentas), Internet Options (Opções da Internet), tab Advanced (Avançado), e habilitar a opção "Use HTTP 1.1".

Para o nosso teste, infelizmente não tenho no momento ferramentas de Monitoring (como por exemplo a Network Monitor Tools, disponibilizada no Windows 2003 server), justamente por estar desenvolvendo este artigo em Windows XP Professional. Apenas para exemplo, vamos verificar através do Trace da página qual dos tipos de compressão foi usado. Vale lembrar que isso não diminui o tamanho final do output da página, ou seja, a finalidade é que o servidor compacta o retorno e envia ao cliente, e este, por sua vez, recebe este conteúdo e automaticamente descompacta e exibe ao usuário e por esta questão é que temos que ter o suporte ao HTTP 1.1.

O projeto constituirá em um Class Library que criaremos uma classe que implementará a interface IHttpModule. Esse processo é bem semelhante o que já fazemos atualmente, ou seja, no evento Init anexamos o procedimento que será disparado quando o evento BeginRequest for invocado. A diferença está na codificação deste procedimento, onde será nele que verificaremos se a requisição que solicitou a página suporta a compressão e assim compactamos o retorno de acordo com os algoritmos suportados pelos browser. Veremos abaixo o componente já implementado:

1
2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Imports Microsoft.VisualBasic
Imports System.IO
Imports System.IO.Compression
Imports System.Web
Public Class Compress
Implements IHttpModule
Public Sub Dispose() Implements System.Web.IHttpModule.Dispose
End Sub
Public Sub Init(ByVal context As HttpApplication) Implements System.Web.IHttpModule.Init
AddHandler context.BeginRequest, AddressOf BeginRequest
End Sub
Private Sub BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
Dim app As HttpApplication = DirectCast(sender, HttpApplication)
Dim headers As String = app.Request.Headers.Get("Accept-Encoding")
If Not headers = String.Empty Then
Dim stream As Stream = app.Response.Filter
If headers.ToUpper.IndexOf("GZIP")> -1 Then
app.Response.Filter = New GZipStream(stream, CompressionMode.Compress)
app.Response.AppendHeader("Content-Encoding", "gzip")
app.Context.Trace.Warn("Compressao via GZIP")
ElseIf headers.ToUpper.IndexOf("DEFLATE")> -1 Then
app.Response.Filter = New DeflateStream(stream, CompressionMode.Compress)
app.Response.AppendHeader("Content-Encoding", "deflate")
app.Context.Trace.Warn("Compressao via DeflateStream")
End If
End If
End Sub
End Class

Código 4 - Implementando a interface IHttpModule para compreesão.


Analisando o código acima, no procedimento BeginRequest recuperamos a instância do objeto HttpApplication e com este, fazemos as devidas consistências, recuperamos e definimos o tipo de retorno. Através do método Get que é fornecida pela propriedade Headers (que contém o cabeçalho da requisição HTTP), recuperamos o Accept-Encoding, que nos informará se o cliente tem ou não suporte aos tipos de compactação do .NET (GZipStream e DeflateStream).

Baseado neste valor, criamos a instância do "compactador" que será utilizado, e com isso, atribuímos ao construtor da classe responsável pela compactação o Stream que é devolvido para o cliente, justamente para internamente, ela criar a versão compactada desta página e devolver para o cliente.

Nesta altura o componente já está criado e com a DLL gerada. Temos agora que adicionar a referência ao nosso projeto ASP.NET Application. Depois de feita a referência, temos que, no arquivo Web.Config desta aplicação ASP.NET, adicionar o HttpModule para que ele possa ser executado nas requisições das páginas.

No exemplo deste artigo, o projeto Class Library foi chamado de "Component" e a classe responsável pela compressão de "Compress". Então adicionamos este módulo no arquivo Web.Config da aplicação, assim como já fazíamos anteriormente, ou seja, dentro da seção "<system.web></system.web>", como é mostrado abaixo:

1
2
3
<httpModules>
<add name="Compress" type="Component.Compress, Component"/>
</httpModules>

Código 5 - Configurando o Módulo no arquivo Web.Config.


Depois disso, ao rodar a aplicação a compactação passa a ser realizada pelo ASP.NET, assim como podemos ver na Figura 1 logo abaixo onde o Trace nos aponta qual dos "compactadores" foi utilizado.

Figura 1 - Verificando qual compactador foi utilizado.

CONCLUSÃO: Através destas classes agora disponíveis nesta nova versão do .NET Framework, podemos criar diversas funcionalidades para as nossas aplicações, sem a necessidade de adquirir um componente de terceiros. Este novo namespace, System.IO.Compression fornece todo o suporte necessário para atingir os objetivos quando se trata de compressão e descompressão de arquivos em .NET.
Israel Aéce

Israel Aéce - Especialista em tecnologias de desenvolvimento Microsoft, atua como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. Como instrutor Microsoft, leciona sobre o desenvolvimento de aplicações .NET. É palestrante em diversos eventos Microsoft no Brasil e autor de diversos artigos que podem ser lidos a partir de seu site http://www.israelaece.com/. Possui as seguintes credenciais: MVP (Connected System Developer), MCP, MCAD, MCTS (Web, Windows, Distributed, ASP.NET 3.5, ADO.NET 3.5, Windows Forms 3.5 e WCF), MCPD (Web, Windows, Enterprise, ASP.NET 3.5 e Windows 3.5) e MCT.