.NET

15 out, 2018

ASP.NET Core 2.1: melhorias na compressão de respostas com Brotli

Publicidade

Já abordei em outro artigo o suporte à compressão de respostas no ASP.NET Core (presente desde o release 1.1), demonstrando o uso deste tipo de técnica baseado no formato GZip em uma API REST:

Desenvolvido inicialmente pela Google e representando uma evolução do padrão GZip, o Brotli é atualmente suportado por browsers como Microsoft Edge, Chrome e Mozilla Firefox.

Neste novo artigo pretendo retomar esta questão envolvendo mecanismos de compressão em aplicações Web, apresentando desta vez uma novidade que integra a versão 2.1 do ASP.NET Core: o suporte ao algoritmo conhecido como Brotli.

Detalhes da aplicação de testes

A aplicação utilizada nos testes descritos neste artigo já está disponível no GitHub. Implementada em ASP.NET Core 2.1, a API REST em questão conta com uma versão que faz uso de GZip e outra de Brotli:

Além de abordar o uso de compressão, este mesmo exemplo demonstra ainda a remoção de valores nulos na resposta produzida por uma API (outro ajuste que pode contribuir significativamente para a geração de um menor volume de dados).

Na listagem a seguir temos a implementação da classe Produto:

namespace APIProdutos.Models
{
    public class Produto
    {
        public string CodProduto;
        public string NomeProduto;
        public double Preco;
        public double? Teste1;
        public double? Teste2;
        public double? Teste3;
        public double? Teste4;
    }
}

Já a próxima listagem traz o código que define o tipo ProdutosController, o qual produzirá a resposta a ser comprimida:

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using APIProdutos.Models;

namespace APIProdutos.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProdutosController : ControllerBase
    {
        [HttpGet]
        public IEnumerable<Produto> Get()
        {
            List<Produto> produtos = new List<Produto>();

            Produto prod;
            for (int i = 1; i <= 100; i++)
            {
                prod = new Produto();
                prod.CodProduto = i.ToString("0000");
                prod.NomeProduto = string.Format("PRODUTO {0:0000}", i);
                prod.Preco = i / 10.0;

                produtos.Add(prod);
            }

            return produtos;
        }
    }
}

Na seguinte imagem é possível observar três cenários envolvendo essa API (os testes empregaram o utilitário Fiddler no monitoramento das respostas geradas):

  • Uma consulta sem remoção de valores nulos e sem compressão de resposta (11.915 bytes);
  • Uma consulta com remoção de valores nulos e sem compressão de resposta (6.315 bytes);
  • Uma consulta com remoção de valores nulos e com compressão de resposta (768 bytes).

Habilitando o uso de Brotli

O primeiro passo para tornar possível a utilização do algoritmo Brotli na compressão de respostas de uma aplicação ASP.NET Core será a codificação de um provider baseado neste formato. Na listagem a seguir está o código que define o tipo BrotliCompressionProvider, com o mesma implementando a interface ICompressionProvider (namespace Microsoft.AspNetCore.ResponseCompression):

using System.IO;
using System.IO.Compression;
using Microsoft.AspNetCore.ResponseCompression;

namespace APIProdutos
{
    public class BrotliCompressionProvider : ICompressionProvider
    {
        public string EncodingName => "br";
        public bool SupportsFlush => true;

        public Stream CreateStream(Stream outputStream) => new BrotliStream(outputStream, CompressionLevel.Optimal, true);
    }
}

Ajustes também serão realizados na classe Startup:

Em ConfigureServices foi especificado o uso da classe BrotliCompressionProvider, através de uma chamada ao método AddResponseCompression (foi definido ainda que este comportamento ocorrerá em conjunto com a utilização de HTTPS).

Ao acionar o método UseResponseCompression em Configure o middleware de compressão de respostas será ativado, empregando para isto as configurações realizadas em ConfigureServices.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.IO.Compression;
using Microsoft.AspNetCore.ResponseCompression;

namespace APIProdutos
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {

            // Configura o modo de compressão
            services.AddResponseCompression(options =>
            {
                options.Providers.Add<BrotliCompressionProvider>();
                options.EnableForHttps = true;
            });

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_0)
                .AddJsonOptions(opcoes => // Remove valores nulos das respostas
                {
                    opcoes.SerializerSettings.NullValueHandling =
                        Newtonsoft.Json.NullValueHandling.Ignore;
                });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }
           

            app.UseHttpsRedirection();

            // Ativa a compressão
            app.UseResponseCompression();
 
            app.UseMvc();
        }
    }
}

Como resultado teremos uma redução para 423 bytes, conforme indicado na próxima imagem:

Referências