ASP.NET Compressão
A compressão de respostas reduz o tamanho do conteúdo que o servidor envia ao cliente, o que economiza banda e acelera o carregamento das aplicações. Neste artigo, vamos entender como esse mecanismo funciona no protocolo HTTP e como habilitá-lo no ASP.NET Core com o middleware nativo.
Ao longo do texto, você vai ver os cabeçalhos envolvidos, os provedores Brotli e Gzip, os níveis de compressão e os cuidados com HTTPS. Os exemplos seguem o modelo com a classe Startup, usado no ASP.NET Core 3.1.
O que é a compressão de respostas
Sempre que fazemos uma requisição para uma aplicação, seja ela ASP.NET, PHP, Java ou qualquer outra, o servidor processa o pedido e envia um retorno. Esse retorno quase sempre chega ao cliente em formato de texto.
Tanto no trabalho com APIs quanto na geração de conteúdo dinâmico no servidor, o resultado costuma ser HTML ou JSON. Além disso, arquivos CSS, JavaScript e XML também trafegam como texto.
Todo texto que sai do servidor tem um tamanho, que podemos diminuir com uma compressão. É a mesma ideia dos arquivos ZIP que usamos nos nossos sistemas operacionais.
Em resumo, o servidor compacta o conteúdo antes de enviar, e o navegador descompacta antes de fazer a leitura. Esse processo é transparente para o usuário final.
Benefícios da compressão de respostas
Habilitar a compressão de respostas reduz o tamanho do retorno do servidor. Com isso, a aplicação consome menos banda e o conteúdo chega mais rápido ao cliente.
A documentação do ASP.NET Core afirma que reduzir o tamanho da resposta costuma aumentar a responsividade da aplicação, muitas vezes de forma dramática. Afinal, a largura de banda da rede é um recurso limitado.
Além disso, a compressão de texto faz parte das auditorias de desempenho do Lighthouse. O guia Enable text compression do web.dev explica como esse ponto impacta a experiência e a avaliação do site.
Como a compressão de respostas funciona no HTTP
Sempre que um navegador consegue processar uma resposta compactada, ele envia o cabeçalho Accept-Encoding com os formatos que aceita. Dessa forma, o servidor sabe quais algoritmos pode usar.
Assim que o servidor recebe uma requisição com esse cabeçalho e decide comprimir, ele adiciona o item Content-Encoding na resposta. Esse cabeçalho informa ao cliente qual algoritmo foi aplicado.
Abaixo estão os valores mais comuns do cabeçalho Accept-Encoding e o suporte de cada um no middleware do ASP.NET Core.
| Accept-Encoding | Suporte no middleware | Descrição |
|---|---|---|
| br | Sim (padrão) | Brotli Compressed Data Format |
| gzip | Sim | Formato GZip |
| deflate | Não | Deflate Compression |
| identity | Sim | A resposta não deve ser comprimida |
| * | Sim | Qualquer formato disponível |
Existe uma lista de diretivas de Accept-Encoding na MDN com mais detalhes sobre cada formato. Além disso, o ASP.NET Core permite criar provedores totalmente customizados.
Pesos e prioridade na compressão de respostas
A negociação de conteúdo foi definida pela RFC 7231, hoje substituída pela RFC 9110. Entre vários detalhes, a especificação define um "peso" para cada formato, escrito com a letra q seguida de um valor.
Por exemplo, podemos indicar que desejamos a compressão via gzip primeiro, depois o identity e, por último, qualquer compressão disponível com o asterisco. Nesse caso, o cliente enviaria o seguinte cabeçalho.
Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0
Como podemos notar, o gzip recebe peso 1, e os demais itens recebem pesos menores. Consequentemente, essa ordem afeta a escolha do formato pelo servidor.
Um valor q=0 significa que o cliente não aceita aquele formato. Portanto, o middleware não comprime a resposta quando o único formato aceito tem peso zero.
O algoritmo de compressão envolve uma troca entre velocidade e efetividade, ou seja, o tamanho final do resultado. O menor tamanho sempre vem da compressão mais otimizada, que também é a mais lenta.
Cabeçalhos de compressão
Vários cabeçalhos participam da compressão de respostas, alguns enviados pelo cliente e outros pelo servidor. A tabela abaixo resume o papel de cada um.
| Header | Descrição |
|---|---|
| Accept-Encoding | Enviado pelo cliente para informar os tipos de compressão aceitos |
| Content-Encoding | Enviado pelo servidor para indicar o tipo de compressão utilizado no resultado |
| Content-Length | Removido quando a compressão ocorre, pois o tamanho do corpo da resposta muda |
| Content-MD5 | Removido quando a compressão ocorre, pois o conteúdo mudou e o hash deixou de valer |
| Content-Type | Define o formato do conteúdo, como HTML, JSON ou XML, e indica se ele pode ser compactado |
| Vary | Com o valor Accept-Encoding, orienta caches a guardar versões separadas da resposta para cada formato |
O cabeçalho Vary merece atenção especial. Sem ele, um proxy poderia entregar uma resposta compactada para um cliente que não sabe descompactá-la.
Quando não usar a compressão de respostas do ASP.NET
Alguns servidores, como IIS, Apache e Nginx, possuem compressão nativa para arquivos JS, CSS, HTML, JSON e XML. Caso o seu servidor já ofereça compressão, não é necessário utilizar o middleware do ASP.NET.
Segundo a documentação oficial, o desempenho do middleware provavelmente não alcança o dos módulos desses servidores. Por outro lado, o Kestrel e o HTTP.sys não oferecem compressão embutida, então o middleware faz sentido quando a aplicação roda diretamente neles.
Além disso, a compressão de respostas é desnecessária para imagens. Formatos comuns como PNG e JPG já são compactados por natureza.
O mesmo vale para arquivos muito pequenos. A documentação recomenda não comprimir arquivos menores que algo entre 150 e 1000 bytes, pois o resultado pode até ficar maior que o original.
Nesses casos, a relação de custo e benefício entre a compressão e o tráfego dos arquivos acaba não compensando. Afinal, o servidor gasta CPU para um ganho quase nulo.
Middleware de compressão de respostas no ASP.NET
Agora que sabemos como funcionam as requisições e respostas comprimidas, vamos implementar esse recurso no ASP.NET. Em suma, precisamos tratar os cabeçalhos das requisições que chegam e das respostas que saem da aplicação.
Para isso, precisamos interceptar essas requisições e respostas. Sempre que falamos em interceptar algo no ASP.NET, utilizamos os middlewares, e o framework já possui um middleware de compressão pronto.
Os middlewares, como o de compressão de respostas, são componentes que rodam entre a chegada da requisição e o envio da resposta. Podemos definir vários deles, e cada um pode adicionar cache, compressão, autenticação e diversas outras funcionalidades.
Eles são executados na ordem em que os adicionamos à aplicação. No final, temos o que chamamos de pipeline, que é o túnel que a requisição percorre até chegar a uma action de um controller, por exemplo.
Se quiser se aprofundar nesse conceito, leia o artigo sobre middleware no ASP.NET aqui do balta.io. Ele mostra como criar o seu próprio componente e como a ordem afeta o resultado.
Instalação
O ASP.NET Core possui o pacote integrado Microsoft.AspNetCore.ResponseCompression. Portanto, não precisamos instalar nada adicional para habilitar a compressão de respostas.
Configuração
Normalmente, configuramos um middleware em duas etapas. Primeiro, adicionamos o serviço no método ConfigureServices e, depois, definimos a execução no método Configure.
Com o middleware de compressão de respostas não é diferente. Toda a configuração inicial que precisamos é esta.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Registra os serviços de compressão com os provedores padrão
services.AddResponseCompression();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Adiciona o middleware ao pipeline
app.UseResponseCompression();
}
}
Como os middlewares rodam na ordem em que são declarados, chame o UseResponseCompression no início do pipeline. Dessa forma, ele envolve os componentes que geram o conteúdo que você quer comprimir.
Sem nenhum provedor explícito, o ASP.NET Core adiciona os provedores Brotli e Gzip automaticamente. Quando o cliente aceita Brotli, ele tem prioridade, e o Gzip entra quando o cliente aceita apenas esse formato.
Compressão de respostas com HTTPS
Existe um detalhe importante que costuma confundir quem testa a compressão de respostas pela primeira vez. Por padrão, o middleware não comprime respostas enviadas por HTTPS.
A opção EnableForHttps controla esse comportamento da compressão de respostas e vem desabilitada. A documentação explica que comprimir páginas dinâmicas sobre conexões seguras pode expor a aplicação a ataques como CRIME e BREACH.
Se você decidir habilitar a opção, faça isso conscientemente e avalie as mitigações, como tokens antifalsificação. A configuração fica assim.
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCompression(options =>
{
// Permite compressão em HTTPS, avalie os riscos antes
options.EnableForHttps = true;
});
}
Vale lembrar também que IIS, IIS Express e Azure App Service podem aplicar Gzip no próprio servidor. Portanto, um Content-Encoding inesperado pode vir do servidor web e não da aplicação.
Testando a compressão de respostas
Após a adição e a configuração do middleware, já podemos executar a aplicação e fazer uma requisição para qualquer URL dela. Para uma melhor visualização, recomendamos testar em uma aplicação ASP.NET MVC, criada com dotnet new mvc.
Os exemplos abaixo usam o curl, mas você pode utilizar outras ferramentas, como Insomnia, Fiddler e Postman. Primeiro, faça a requisição sem nenhuma compressão especificada.
curl -i https://localhost:5001
Como retorno, podemos esperar o HTML padrão das aplicações ASP.NET MVC, totalmente legível. Nesse caso, a resposta não traz o cabeçalho Content-Encoding.
Porém, se informarmos o Accept-Encoding: br no cabeçalho, devemos receber um retorno compactado e ilegível. Lembre que, em HTTPS, isso só acontece com o EnableForHttps habilitado.
curl -i -H "Accept-Encoding: br" https://localhost:5001
Na resposta compactada, aparecem o cabeçalho Content-Encoding com o valor br e o cabeçalho Vary com o valor Accept-Encoding. Esse é o sinal de que a compressão de respostas está funcionando.
Algumas ferramentas descompactam automaticamente as respostas e exibem o HTML formatado. Por isso, verifique sempre o tamanho das respostas e os cabeçalhos retornados.
Utilizando outros provedores
No exemplo anterior, usamos a configuração padrão. Contudo, como vimos no início, podemos escolher explicitamente os formatos, como Brotli e Gzip, ou até criar um provedor próprio.
Para isso, configuramos a compressão de respostas com as opções do método AddResponseCompression e com Providers.Add. O cliente indica sua preferência pelo Accept-Encoding, e o middleware escolhe entre os provedores registrados.
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCompression(options =>
{
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.Providers.Add<CustomCompressionProvider>();
});
}
Atenção: quando você adiciona qualquer provedor explicitamente, os provedores padrão deixam de entrar sozinhos. Portanto, se quiser manter Brotli e Gzip, registre os dois junto com o provedor customizado.
Criando um provedor customizado
Um provedor customizado implementa a interface ICompressionProvider. A propriedade EncodingName define o nome do formato que o cliente precisa enviar no Accept-Encoding.
public class CustomCompressionProvider : ICompressionProvider
{
public string EncodingName => "mycustomcompression";
public bool SupportsFlush => true;
public Stream CreateStream(Stream outputStream)
{
// Envolva o stream de saída com o seu algoritmo de compressão
return outputStream;
}
}
Com esse provedor, uma requisição com Accept-Encoding: mycustomcompression recebe a resposta com Content-Encoding: mycustomcompression. Obviamente, o cliente também precisa saber descompactar esse formato.
Alterando o nível de compressão
Conforme comentamos no começo, a compressão envolve uma troca entre velocidade e tamanho final. Por padrão, os provedores Brotli e Gzip usam o nível CompressionLevel.Fastest.
Para ter controle sobre o nível de compressão de respostas, podemos configurar o Level de cada provedor. Veja o exemplo com o Brotli.
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCompression();
services.Configure<BrotliCompressionProviderOptions>(options =>
{
// Prioriza a velocidade da compressão
options.Level = CompressionLevel.Fastest;
});
}
Dessa forma, o ASP.NET Core sempre opta pela compressão de respostas mais rápida, mesmo que o resultado não fique tão compacto. Para o Gzip, usamos a classe GzipCompressionProviderOptions da mesma maneira.
Abaixo segue a tabela com os níveis de compressão disponíveis no ASP.NET Core 3.1.
| Nível | Descrição |
|---|---|
| CompressionLevel.Fastest | Favorece a performance |
| CompressionLevel.NoCompression | Sem compressão |
| CompressionLevel.Optimal | Favorece a compressão |
Se o objetivo for o menor tamanho possível, configure o nível Optimal. Em troca, o servidor gasta mais tempo de CPU em cada resposta.
Tipos de documento na compressão de respostas
Por padrão, a compressão de respostas atua sobre os seguintes tipos de documento, também chamados de MIME types.
| MIME type | Conteúdo |
|---|---|
| application/javascript | Scripts JavaScript |
| application/json | Respostas JSON de APIs |
| application/xml | Documentos XML |
| text/css | Folhas de estilo |
| text/html | Páginas HTML |
| text/json | JSON como texto |
| text/plain | Texto puro |
| text/xml | XML como texto |
O middleware verifica o Content-Type da resposta para decidir se comprime ou não. Por isso, toda resposta deve informar o seu tipo de conteúdo.
Caso queira comprimir algum tipo não listado, você pode estender a lista com a propriedade MimeTypes. O exemplo abaixo adiciona o formato SVG aos tipos padrão.
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCompression(options =>
{
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
// Mantém os tipos padrão e acrescenta SVG
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "image/svg+xml" });
});
}
Note que o middleware não aceita curingas como text/*. Portanto, cada tipo precisa aparecer de forma explícita.
Problemas comuns
Quando o Nginx atua como proxy reverso, ele remove o cabeçalho Accept-Encoding e impede a compressão de respostas pelo ASP.NET. Sem esse cabeçalho, o middleware não comprime a resposta, e a compressão precisa acontecer no próprio Nginx.
Outro cenário envolve o IIS com o módulo de compressão dinâmica ativo. Nesse caso, desabilite o módulo para a aplicação ou deixe a compressão apenas com o servidor, evitando trabalho duplicado.
Por fim, requisições com o cabeçalho Content-Range também não recebem compressão. O mesmo acontece quando o Accept-Encoding traz apenas identity ou formatos com peso zero.
Conclusão
A compressão de respostas é uma forma simples de reduzir o tráfego e acelerar aplicações ASP.NET Core. Com duas linhas na classe Startup, o middleware passa a entregar conteúdo em Brotli ou Gzip conforme o cliente aceita.
Vimos também como os cabeçalhos HTTP orientam essa negociação e como ajustar provedores, níveis e tipos de documento. Além disso, entendemos por que o HTTPS vem desabilitado por padrão.
Por outro lado, lembre que servidores como IIS e Nginx podem fazer esse trabalho com mais eficiência. Avalie a sua hospedagem e escolha o ponto certo do pipeline para comprimir.
FAQ
Preciso instalar algum pacote para usar a compressão de respostas no ASP.NET Core?
Não. O pacote Microsoft.AspNetCore.ResponseCompression já faz parte do framework, então basta registrar o serviço e o middleware.
Por que minha API em HTTPS não está comprimindo as respostas?
A opção EnableForHttps vem desabilitada por padrão por causa dos riscos de ataques como CRIME e BREACH. Habilite somente após avaliar esses riscos.
Qual é melhor, Brotli ou Gzip?
O Brotli costuma gerar arquivos menores e é o padrão do middleware. O Gzip funciona como alternativa para clientes que não aceitam Brotli.
Vale a pena comprimir imagens PNG e JPG?
Não. Esses formatos já são compactados, então a nova compressão gasta CPU sem reduzir o tamanho de forma relevante.
Como saber se a resposta foi comprimida?
Verifique se a resposta traz o cabeçalho Content-Encoding com o formato usado e compare o tamanho do conteúdo recebido.