Asp.net Core PERFORMANCE - ETag
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
Pronto! Qualquer alteração no html ou nos dados retornardos irá gerar uma nova ETag
.
Espero ter ajudado!
[]s e até a próxima.