Olá pessoALL,

Em um dos clientes que trabalho temos feito algumas melhorias de performance no código e temos conseguido alguns resultados muito interessantes, quero compartilhar alguns com vocês.

A primeira melhoria foi o use do header ETag nas nossas respostas. De uma maneira bem simplificada, o browser checa se na resposta de uma requisição existe o header ETag e se existir na próxima requisição o valor é enviado no header If-None-Match, do lado do servidor checamos se os valores são iguais e em caso positivo devolvemos uma resposta com o status code 304 - Not Modified e com o body vazio.

Note que ainda não melhoramos o código executado no servidor, essa técnica serve para economizar dados trafegados na rede, uma vez que não precisamos devolver o resultado da requisição e "falamos" para o browser que ele ja tem o conteúdo que precisa e pode usa-lo novamente.

Otimizar o tempo de execução das chamadas será assunto de um outro post, mas chega de teoria e vamos ao código.

Todo o código esta no github: https://github.com/rsantosdev/aspnetcore-etagger

1- O middleware

    public class ETagMiddleware
    {
        private readonly RequestDelegate _next;

        public ETagMiddleware(RequestDelegate next) => _next = next;

        public async Task InvokeAsync(HttpContext context)
        {
            var response = context.Response;
            var originalStream = response.Body;

            using (var ms = new MemoryStream())
            {
                response.Body = ms;

                await _next(context);

                if (IsEtagSupported(response))
                {
                    var checksum = CalculateChecksum(ms);

                    response.Headers[HeaderNames.ETag] = checksum;

                    if (context.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var etag) && checksum == etag)
                    {
                        response.StatusCode = StatusCodes.Status304NotModified;
                        response.Headers[HeaderNames.ContentLength] = "0";
                        response.ContentType = null;
                        response.Body = null;
                        return;
                    }
                }

                ms.Position = 0;
                await ms.CopyToAsync(originalStream);
            }
        }

        private static bool IsEtagSupported(HttpResponse response)
        {
            if (response.StatusCode != StatusCodes.Status200OK)
            {
                return false;
            }

            // The 40kb length limit is not based in science. Feel free to change
            if (response.Body.Length > 40 * 1024)
            {
                return false;
            }

            if (response.Headers.ContainsKey(HeaderNames.ETag))
            {
                return false;
            }

            return true;
        }

        private static string CalculateChecksum(MemoryStream ms)
        {
            var checksum = "";

            using (var algo = SHA1.Create())
            {
                ms.Position = 0;
                byte[] bytes = algo.ComputeHash(ms);
                checksum = $"\"{WebEncoders.Base64UrlEncode(bytes)}\"";
            }

            return checksum;
        }
    }

O código é bem simples, checa pelos headers, calcula o hash da resposta e em caso positivo retorna um valor 304 com um body vazio.

2 - O Uso

Para adicionar o middleware é bem simples:

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            // removed for simplicity

            app.UseMiddleware<ETagMiddleware>();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }

3 - Na prática

Observe na prática o uso do middleware e observe a redução de tamanho da resposta da requisição

etag_headers

Pronto! Qualquer alteração no html ou nos dados retornardos irá gerar uma nova ETag.

Espero ter ajudado!
[]s e até a próxima.