ASP.NET Response Compression
O Response Compression é o recurso que comprime as respostas da sua aplicação antes de enviá-las ao navegador, e o ASP.NET Core traz um middleware pronto para isso. Neste artigo, vamos entender o que é a compressão de respostas e como utilizá-la para aumentar a performance das aplicações ASP.NET.
Ao longo do texto, você vai ver os cabeçalhos HTTP envolvidos, a configuração no ASP.NET Core 3.1, os providers Brotli e Gzip e os níveis de compressão disponíveis.
O que é o Response Compression
Sempre que fazemos uma requisição para uma aplicação, seja ela ASP.NET, PHP, Java ou qualquer outra, o servidor processa a requisição e nos envia um retorno.
Tanto no trabalho com APIs quanto na geração de conteúdo dinâmico no servidor, o retorno quase sempre é um texto. Às vezes HTML, às vezes JSON.
Todo texto que sai do servidor, por sua vez, tem um tamanho. Esse tamanho diminui com uma compressão, assim como fazemos com os arquivos .ZIP em nossos sistemas operacionais.
Habilitar o Response Compression reduz o tamanho do retorno do servidor, o que consome menos banda e deixa as respostas mais rápidas de trafegar. Além disso, a compressão de texto é uma das auditorias de performance do Lighthouse, o que pode melhorar o rankeamento do site.
Em resumo, o conteúdo de retorno é zipado no servidor e o navegador descompacta esse conteúdo antes de fazer a leitura.
Valores do Accept-Encoding
Abaixo estão os possíveis valores para o cabeçalho da requisição que o middleware do ASP.NET Core 3.1 suporta.
| Accept-Encoding | Descrição |
|---|---|
| br (padrão) | Brotli Compressed Data Format |
| gzip | Formato Gzip |
| identity | A resposta NÃO DEVE ser comprimida |
| * | Qualquer formato disponível |
Também existem formatos como deflate, exi e pack200-gzip. No entanto, o middleware não suporta esses valores nativamente.
Existe uma lista de tipos de compressores no MDN. Além disso, podemos criar um compressor totalmente customizado, como veremos adiante.
Pesos e prioridade com o parâmetro q
A especificação do cabeçalho estava na RFC 7231, hoje substituída pela RFC 9110. Entre vários detalhes, ela define o peso que ordena as preferências, especificado por um q seguido do valor.
Por exemplo, podemos dizer que desejamos tentar a compressão via gzip primeiro, seguida pelo identity. Por fim, o * cobre qualquer compressão disponível.
Nesse caso, o cliente envia o seguinte cabeçalho:
Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0
Como podemos notar, definimos gzip;q=1.0 com peso 1 e vamos diminuindo o peso dos demais itens. Consequentemente, essa ordem afeta a escolha do algoritmo.
O algoritmo de compressão envolve um custo-benefício entre velocidade e efetividade, ou seja, o tamanho final do resultado. O menor tamanho final sempre resulta da compressão mais otimizada.
Quando usar o Response Compression
Nem toda aplicação precisa do middleware de compressão. Por isso, vale entender em quais cenários ele realmente compensa.
Compressão nativa do servidor
Alguns servidores, como IIS, Apache e Nginx, possuem compressão nativa para arquivos como JS, CSS, HTML, JSON e XML. Caso seu servidor já ofereça compressão, não é necessário utilizar o Response Compression do ASP.NET.
Segundo a documentação oficial de compressão de respostas, a performance do middleware provavelmente não alcança a dos módulos do servidor. Por outro lado, o Kestrel e o HTTP.sys não oferecem compressão embutida.
Portanto, o Response Compression faz sentido quando a aplicação roda direto no Kestrel ou no HTTP.sys. Da mesma forma, ele ajuda quando você não consegue usar os módulos de compressão do IIS, do Apache ou do Nginx.
O que não vale a pena comprimir
Em adicional, é desnecessário comprimir imagens. Os formatos mais comuns, como PNG e JPG, já chegam compactados.
O mesmo vale para arquivos muito pequenos. A documentação recomenda não comprimir arquivos menores que algo entre 150 e 1000 bytes, porque o resultado pode ficar maior que o original.
Nesses casos, a relação custo benefício entre a compressão e o tráfego dos arquivos acaba não compensando. Afinal, o tempo gasto para comprimir supera a pequena economia de bytes.
Cabeçalhos de compressão
A tabela abaixo resume os cabeçalhos que participam do processo.
| Header | Descrição |
|---|---|
| Accept-Encoding | Enviado pelo cliente para definir os tipos de compressão aceitos |
| Content-Encoding | Enviado pelo servidor para informar a compressão utilizada no resultado |
| Content-Length | Removido quando a compressão ocorre, porque o corpo da resposta muda de tamanho |
| Content-MD5 | Removido quando a compressão ocorre, porque o hash do corpo deixa de valer |
| Content-Type | Define o formato do conteúdo (HTML, JSON, XML) e diz se ele pode ou não ser compactado |
| Vary | Com o valor Accept-Encoding, orienta clientes e proxies a guardar em cache versões separadas da resposta |
O Vary merece atenção especial. Graças a ele, um proxy não entrega uma resposta em Brotli para um cliente que só entende Gzip.
Response Compression no ASP.NET Core
Agora que sabemos o que são as compressões de respostas e como funcionam as requisições e respostas comprimidas, vamos implementar esse recurso com o ASP.NET.
Em suma, precisamos tratar os cabeçalhos das requisições e respostas que entram e 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. Para nossa felicidade, o ASP.NET já possui um middleware de compressão.
O que é um middleware
Middlewares são agentes executados entre as requisições e as nossas aplicações. Podemos definir vários middlewares, que agem em uma requisição adicionando cache, compressão e diversas outras coisas.
Os middlewares executam na ordem em que os adicionamos à aplicação. No final, temos o que chamamos de pipeline, o túnel que a requisição percorre até chegar a uma action de um controller, por exemplo.
Se quiser se aprofundar no tema, veja o artigo sobre middleware no ASP.NET e como criar o seu.
Instalação
O ASP.NET Core possui um pacote integrado chamado Microsoft.AspNetCore.ResponseCompression. Então, não precisamos instalar nada adicional para usar o Response Compression.
Esse pacote já faz parte do shared framework Microsoft.AspNetCore.App, que todo projeto web referencia.
Configuração
Normalmente, configuramos um middleware adicionando seu serviço no método ConfigureServices e definindo sua execução no método Configure. Com o Response Compression não é diferente.
Dessa forma, toda configuração inicial que precisamos é esta:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// registra os serviços de compressão com os providers padrão
services.AddResponseCompression();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// deve vir antes de qualquer middleware que gere respostas
app.UseResponseCompression();
}
}
Como comentado anteriormente, os middlewares executam na ordem em que aparecem. Por isso, é importante chamar o UseResponseCompression antes de qualquer outro middleware que escreva a resposta.
Sem nenhuma opção, o AddResponseCompression registra os providers Brotli e Gzip. Assim, o Brotli vira o padrão quando o cliente o suporta, e o Gzip entra como alternativa.
Compressão com HTTPS e segurança
Existe um detalhe que pega muita gente de surpresa. Por padrão, o middleware não comprime respostas enviadas por HTTPS.
Esse comportamento depende da opção EnableForHttps, que começa desativada. Segundo a documentação, comprimir páginas dinâmicas sobre conexões seguras pode expor a aplicação a ataques como CRIME e BREACH.
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCompression(options =>
{
// habilita a compressão em HTTPS, avalie os riscos antes
options.EnableForHttps = true;
});
}
Portanto, ative essa opção apenas depois de avaliar os riscos. Em páginas com dados sensíveis, tokens antiforgery ajudam a mitigar esses ataques.
Testando o Response Compression com curl
Após a adição e configuração do pacote de compressão, já podemos executar a aplicação e fazer uma requisição para qualquer URL dela.
Para uma melhor visualização, recomendamos os testes em uma aplicação ASP.NET MVC, criada com dotnet new mvc, por exemplo. Como os testes usam HTTPS, lembre de ativar o EnableForHttps no ambiente de desenvolvimento.
Com tudo pronto, vamos realizar a requisição sem nenhuma compressão especificada. Os exemplos abaixo usam o curl, porém você pode utilizar outras ferramentas, como Insomnia, Fiddler e Postman.
curl -i -H "Content-Type: text/html" https://localhost:5001
Como retorno, podemos esperar o HTML padrão das aplicações ASP.NET MVC, totalmente legível para nós.
Porém, se informarmos o Accept-Encoding: br no cabeçalho, devemos esperar um retorno compactado e ilegível.
curl -i -H "Accept-Encoding: br" -H "Content-Type: text/html" https://localhost:5001
Na resposta, os cabeçalhos Content-Encoding: br e Vary: Accept-Encoding confirmam que o Response Compression entrou em ação.
IMPORTANTE: algumas ferramentas descompactam as respostas automaticamente e exibem o HTML formatado. Verifique sempre o tamanho (Size) das respostas.
Providers do Response Compression
No exemplo anterior, vimos a compressão padrão. Porém, como vimos na primeira seção deste artigo, podemos escolher explicitamente formatos como o Brotli e o Gzip.
Para isso, usamos as opções do método AddResponseCompression com o método Providers.Add<PROVIDER>. O middleware considera os providers na ordem em que os adicionamos.
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCompression(options =>
{
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.Providers.Add<CustomCompressionProvider>();
});
}
Atenção a um detalhe importante: quando você adiciona qualquer provider explicitamente, o ASP.NET deixa de registrar os padrões. Então, inclua o Brotli e o Gzip manualmente se quiser mantê-los.
Criando um provider customizado
Um provider customizado implementa a interface ICompressionProvider. A propriedade EncodingName define o valor 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)
{
// aqui entra o stream com o algoritmo de compressão customizado
return outputStream;
}
}
Nesse exemplo, o stream não comprime nada, apenas mostra onde o algoritmo entraria. Além disso, o cliente precisa saber descompactar o formato customizado para tudo funcionar.
Alterando o nível de compressão do Response Compression
Por fim, conforme comentamos no começo deste artigo, o ASP.NET toma conta da decisão sobre a otimização da compressão. Por padrão, os providers Brotli e Gzip usam o nível mais rápido.
Para alterar esse fator e controlar o nível de compressão, podemos especificar o CompressionLevel dentro das opções de cada provider.
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 mais rápida possível, mesmo que o provider consiga compactar mais. Da mesma forma, o GzipCompressionProviderOptions controla o nível do Gzip.
Abaixo segue a tabela com as opções de 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 |
Na prática, o Optimal gera respostas menores, mas consome mais CPU em cada requisição. Por isso, meça o impacto antes de mudar o padrão em produção.
Tipos de documento
Por padrão, o Response Compression atua nos seguintes tipos de documento (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 |
Caso queira comprimir algum tipo de documento fora dessa lista, use a propriedade MimeTypes das opções. O exemplo abaixo, baseado na documentação oficial, acrescenta o SVG.
services.AddResponseCompression(options =>
{
// mantém os tipos padrão e adiciona o SVG
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "image/svg+xml" });
});
Vale lembrar que curingas como text/* não funcionam nessa lista. Para formatos totalmente novos, você também pode criar um provider customizado, como mostra a documentação oficial do ASP.NET.
Problemas comuns com o Response Compression
Quando a compressão não acontece, a causa geralmente está em uma destas condições:
- O cabeçalho
Accept-Encodingnão chegou, ou chegou comidentityouq=0. - O
Content-Typeda resposta não está na lista de MIME types configurada. - A requisição usa HTTPS e a opção
EnableForHttpscontinua desativada. - A requisição inclui o cabeçalho
Content-Range.
Além disso, o Nginx remove o cabeçalho Accept-Encoding quando atua como proxy reverso. Nesse caso, o middleware não comprime nada, e o ideal é deixar a compressão a cargo do próprio Nginx.
No IIS, o módulo de compressão dinâmica também pode comprimir as respostas por conta própria. Então, se aparecer um Content-Encoding inesperado, confira se o servidor não está fazendo esse trabalho.
Imagens geradas dinamicamente, como as do artigo sobre QRCode no ASP.NET, também não precisam passar pelo middleware. Afinal, o PNG já sai compactado.
Conclusão
O Response Compression é uma forma simples de reduzir o tamanho das respostas e economizar banda. Com duas linhas, AddResponseCompression e UseResponseCompression, o ASP.NET Core passa a entregar HTML, JSON e CSS compactados.
Em resumo, use o Response Compression quando o servidor não oferecer compressão nativa, escolha bem os providers e o nível e avalie com cuidado a compressão em HTTPS.
FAQ
Preciso instalar algum pacote para usar o Response Compression?
Não, porque o pacote Microsoft.AspNetCore.ResponseCompression já faz parte do shared framework do ASP.NET Core. Basta registrar o serviço e o middleware.
Por que minha resposta em HTTPS não está comprimida?
A opção EnableForHttps começa desativada por causa dos ataques CRIME e BREACH. Ative a opção nas configurações do middleware depois de avaliar os riscos.
Qual provider o ASP.NET Core usa por padrão?
Sem configuração explícita, o middleware registra Brotli e Gzip. O Brotli tem prioridade quando o cliente o suporta.
Devo comprimir imagens?
Não, formatos como PNG e JPG já são compactados. Comprimir de novo gasta CPU e quase não reduz o tamanho.
Posso usar o middleware atrás do Nginx?
Pode, mas o Nginx remove o Accept-Encoding da requisição. Nesse cenário, prefira configurar a compressão no próprio Nginx.