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.

Glossário

Termo Significado
Brotli Algoritmo de compressão definido pela RFC 7932 e padrão no middleware do ASP.NET Core.
Gzip Formato de compressão definido pela RFC 1952 e amplamente suportado pelos navegadores.
Middleware Componente que intercepta requisições e respostas no pipeline do ASP.NET Core.
Pipeline Sequência de middlewares que a requisição percorre até gerar a resposta.
MIME type Identificador do tipo de conteúdo de uma resposta, como text/html ou application/json.
qvalue Peso informado no Accept-Encoding para indicar a preferência do cliente.
BREACH Ataque que explora a compressão de respostas em conexões HTTPS para extrair segredos.
Kestrel Servidor web multiplataforma embutido no ASP.NET Core.

Fontes