Asp.NET Core – ETag Nedir? ETag İle Response Caching İşlemi Nasıl Gerçekleştirilir?

Merhaba,

Bu içeriğimizde client ile server arasındaki ilişkiyi daha performanslı ve server’da ki iş yükünü daha az maliyetli hale getirmek için düşünülmüş olan ve HTTP response’unda client’a gönderilen bir bilgi olarak karşımıza çıkan ETag üzerine detaylıca konuşuyor olacak ve ilgili konunun ne olduğunu, işlevsel açıdan nelere yaradığını, önbellekleme açısından ne tarz amaçlarla kullanıldığını ve biz yazılım geliştiricileri olarak bu bilgiyi lehimize ne şekilde ve nasıl kullanabileceğimizi inceleyecek ve irdeleyeceğiz. Buyrun, hiç vakit kaybetmeden konumuzu detaylandıralım…

ETag Nedir?

ETag; Entity Tag – Varlık Etiketi

ETag, client tarafından yapılan istek neticesinde üretilen datanın güncellik durumunu ifade etmek ve client’ı bilgilendirmek için server tarafından üretilen ve HTTP response’unda taşınan bir bilgidir.

Genellikle web sunucular, içeriğini statik olarak kabul ettikleri dosyalar(.js, .html, .jpg, .png vs.) için ETag’i otomatik üretmekte ve HTTP response’larında göndermektedirler. Bu duruma sözgelimi olarak bu blog’ta ki(gencayyildiz.com/blog) herhangi bir görseli elle alabilir ve response header’ını inceleyebiliriz.
Asp.NET Core - ETag Nedir? ETag İle Response Caching İşlemi Nasıl Gerçekleştirilir?
Görüldüğü üzere sayfaya yüklenen herhangi bir görsel(.jpg) sunucu tarafından ‘etag’ bilgisiyle gönderilmiştir. Bu bilgi yukarıda bahsedildiği gibi sunucu tarafından otomatik oluşturulmaktadır.

Evet… ETag’ın sunucu tarafından oluşturulduğunu ve hatta sunucu tarafında statik kabul edilen dosyalar için otomatik oluşturulduğunu öğrenmiş olduk. Şimdi asıl sorularımıza gelelim. Neden oluşturulduğuna ve nasıl kullanıldığına?

ETag Neden Oluşturulur?

ETag, client’ın server’a yaptığı istek neticesinde gelecek olan datanın güncel olup olmama durumunu bizlere bildirmek için kullanılan bir değerdir. Neden mi? Çünkü genellikle server’da üretilen datalar, sonraki isteklerde performansı arttırmak ve maliyeti düşürmek amacıyla önbelleklenmekte(caching) ve ilk istekten sonrakilerde bu önbellekteki datalar client’a gönderilmektedir.

Bu önbellekleme sonrası süreçlerde bu dataların sık sık yenilenmesi yahut güncellenmesi gibi oldukça doğal fiziksel durumlar söz konusu olabilir. İşte bu durumlarda güncellemelerden client’ın haberi olmayacağından ve önbelleğe alınmış verinin ne zaman yenileneceğini veya değişeceğini bilemeyeceğinden dolayı her request’e karşılık önbellekteki verilerin taa ki önbellekleme süresi sona erinceye kadar birebir aynı şekilde gelmesi söz konusu olacaktır. Bu şekilde önbelleklenmiş datalar ile API’dan gelecek olan dataların sürekli aynı olması, client ile server arasındaki veri akışında lüzumsuz bir trafiğe sebep olacak ve süreci olumsuz etkileyecektir. Burada ideal olan, client tarafından yapılan istek neticesinde gönderilecek datanın önceden önbelleklenmiş datadan farklı olması kaydıyla API’dan gönderilmesini sağlamak, aksi taktirde zaten var olan datanın tekrar gönderilmeksizin kullanımına devam edilmesini client’a bildirmektir.

Burada misal olarak bir şirketteki yapılan satışların liste olarak getirildiği herhangi bir uygulamanın senaryosunu düşünelim. Uygulamanın yapılan her istek neticesinde satışları daha hızlı listeleyebilmek için bu dataları önbelleğe aldığını varsayalım. Önbellekleme işleminin süresi dolana kadar yapılan tüm isteklerde uygulamaya datalar önbellek üzerinden gitmekte, süre bittikten sonraki ilk istekte ise tekrar veritabanından çekilip önbelleklenmektedir. İşte burada kritik yapmaya değer iki durum söz konusu olmaktadır.

Birinci durum; Önbellekleme süresi içerisinde datalar ya değiştiyse/güncellendiyse?
Önbellekleme süresi içerisinde datalar fiziksel olarak değişti yahut güncellendiyse eğer uygulama hala önbellekteki datalarla çalışamaya devam edecektir. Bu durum eksik verilerle çalışmayı gerektireceğinden dolayı tutarsız sonuçlara ve yanlış hesaplamalara sebep olacaktır. Yani amiyane tabirle bir handikaptır. Burada güncel verilerin tekrar veritabanından çekilmesi ve client’a gönderilmesi gerekmektedir.

İkinci durum; Önbellekleme sürecinden sonraki datalar ya henüz değişmediyse/güncellenmediyse?
Önbellekleme süresi bittiği taktirde datalar yeniden veritabanından çekilecektir. Haliyle herhangi bir fiziksel değişiklik yahut güncelleme olmaması durumunda çekilen bu dataların client tarafına gönderilmesi tekrardan lüzumsuz bir maliyete sebep olacaktır. Burada da elde edilen data ile client’ta ki arasında farklılık varsa eğer gönderilmesi, aksi taktirde client’a elindekiyle yetinmesi bildirilmelidir.

İşte tüm bu süreçlerde server tarafında üretilen ETag değeri, datanın güncel olup olmama durumuna istinaden client üzerinden server’ı bilgilendirmek için kullanılan bir değerdir.

ETag Nasıl Çalışır?

ETag; sunucu tarafında üretilen datanın, client’taki mevcut data ile aralarında olan farkı ortaya koyabilmek için, ilgili dataya sunucu tarafından bir versiyon/sürüm bilgisi üretilmesi ve client’dan gelen isteklerde bu versiyon/sürüm bilgisini alarak yeni üretilen yahut önbellekten çekilen datanın versiyon/sürüm bilgisiyle kıyaslanarak güncellik durumuna istinaden client’ın bilgilendirilmesi esasına dayanan bir mantığa sahip etikettir.

ETag’da amaç; API tarafından client’ta var olan verinin lüzumsuz yere tekrar gönderilmesini engellemek ve böylece client’ın cache(önbellek)teki veriyi kullanmasına devam etmesini sağlamaktır.

Bu versiyon/sürüm istek neticesinde geçerliliğini yitirmişse datalar güncellenmiş yahut değiştirilmiş demektir. Bu durumda da ilgili datalar yeni bir versiyon/sürüm bilgisiyle tekrar üretilerek client’a gönderilmelidir.

İşte burada bahsedilen versiyon/sürüm esasında ETag değeridir.

Client İle Server Arasında ETag İşlevi

Asp.NET Core - ETag Nedir? ETag İle Response Caching İşlemi Nasıl Gerçekleştirilir?ETag, basitçe bir dizeden ibaret değerdir. Bu değer, client’ın sahip olduğu verilerin sürüm bilgisini temsil eder ve client bu sürüm bilgisini sunucuda herhangi bir yeni sürümün oluşturulup oluşturulmadığını kontrol etmek için kullanır. Sunucu, client tarafında bulunan sürümden farklı yeni bir sürüm değerine sahip başka bir veri üretiyorsa eğer bunu client’a yeni sürüm kodu eşliğinde ‘200 Status Code’u ile iletecektir. Aksi taktirde client’ta ki sürümden farklı bir sürümde veri üretilmiyorsa ‘304 Status Code’ değeri ile yanıt verecektir.

Sunucudan gelen ETag değerini client, request header’ın ‘If-None-Match’ parametresiyle tekrar sunucuya iletir. Sunucu, istemciden gelen bu parametreyle o anda ki veriler için üretilen ETag değerini karşılaştırır. Bu karşılaştırma neticesinde bir değişiklik yoksa verilerin değişmediği ve client’ın local storage’ının güncellenmesinin gerekmediği anlaşılacak ve 304(Not Modified) durum koduyla bunu client’a bildirecektir. Bu döngü, sunucu tarafında kaynak değişene kadar devam edecektir.

Peki sunucu ETag değerini nasıl üretir?
Tüm bu süreçte client’ın ETag olarak kabul ettiği dizge hakkında hiçbir fikri yoktur ve JWT gibi özel bir çözümleyici gerektirmediği için sunucu tarafından bu ETag her türlü mekanizma yahut algoritma ile üretilebilir.

Asp.NET Core API İle Basit Bir ETag Uygulaması

Asp.NET Core uygulamalarında client’tan gelen isteğe göre ETag kontrolü gerçekleştirmek istiyorsanız eğer aşağıdaki gibi bir attribute tasarlamanız oldukça elverişli olacaktır. (Attribute dışında middleware gibi çözümlere de başvurabilirsiniz. Lakin konuya dair en efektif çözümün attribute olduğu kanaatindeyim…)

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
    public class ETagFilterAttribute : ActionFilterAttribute
    {
        //İstek neticesinde ilgili Action tetiklendikten sonra bu metot tetiklenir.
        public override void OnActionExecuted(ActionExecutedContext context)
        {
            //Gelen isteğin GET ve tetiklenen Action metodun sonucunun 200 olduğunda;
            if (context.HttpContext.Request.Method == "GET" && context.HttpContext.Response.StatusCode == (int)HttpStatusCode.OK)
            {
                //context.Result -> Tetiklenen Action metodun neticesinde üretilen geriye döndürülecek
                //olan değeri ifade etmektedir. JsonConvert.SerializeObject metodu ile(Newtonsoft)
                //bu değer serilize edilerek string vaziyette elde edilmektedir.
                string resultData = JsonConvert.SerializeObject(context.Result);

                //Yukarıdaki resultData kullanılarak bir ETag üretiliyor. Bu ETag değeri, resultData değerine göre tekil üretilecektir. Dolayısıyla ilgili action'a gelen tüm isteklerde resultData aynı oldukça her seferinde aynı değer üretilecektir.
                string etag = ETagGenerator.GetETag(context.HttpContext.Request.Path.ToString(), Encoding.UTF8.GetBytes(resultData));

                //Gelen istek header'ında 'IfNoneMatch' parametresi var mı? kontrol ediliyor;
                if (context.HttpContext.Request.Headers.Keys.Contains(HeaderNames.IfNoneMatch))
                {
                    //Header'dan 'IfNoneMatch' değeri alınıyor.
                    string inComingETag = context.HttpContext.Request.Headers[HeaderNames.IfNoneMatch].ToString();

                    //Üretilen ETag ile header'dan gelen eşleştiriliyor;
                    if (inComingETag.Equals(etag))
                        //Eğer aynı ise geriye 304 Status Code değeri döndürülüyor.
                        context.Result = new StatusCodeResult((int)HttpStatusCode.NotModified);
                }
                //Üretilen ETag değeri response header'ına ETag parametresi karşılığında yerleştiriliyor.
                context.HttpContext.Response.Headers.Add(HeaderNames.ETag, new[] { etag });
            }

            base.OnActionExecuted(context);
        }
    }

Görüldüğü üzere ilgili attribute’un işlevsel açıdan gerekli tüm açıklamaları satır aralıklarında belirtilmiş vaziyettedir. Burada ETag üretiminden sorumlu ‘ETagGenerator’ sınıfının içeriğini de aşağıya alalım;

    static public class ETagGenerator
    {
        //Rastgele binary kombin oluşturuyoruz.
        private static byte[] Combine(byte[] a, byte[] b)
        {
            byte[] c = new byte[a.Length + b.Length];
            Buffer.BlockCopy(a, 0, c, 0, a.Length);
            Buffer.BlockCopy(b, 0, c, a.Length, b.Length);
            return c;
        }
        //Binary değerden hashlenmiş değer üretiyoruz.
        private static string GenerateETag(byte[] data)
        {
            using MD5 md5 = MD5.Create();
            byte[] hash = md5.ComputeHash(data);
            string hex = BitConverter.ToString(hash);
            return hex.Replace("-", "");
        }
        public static string GetETag(string key, byte[] contentBytes)
        {
            byte[] keyBytes = Encoding.UTF8.GetBytes(key);
            var combinedBytes = Combine(keyBytes, contentBytes);
            return GenerateETag(combinedBytes);
        }
    }

Şimdi ilgili attribute’u kullanarak ETag kontrolü yapacağımız controller ve action’ı geliştirelim.

    [Route("api/[controller]")]
    [ApiController]
    public class OrdersController : ControllerBase
    {
        [ETagFilter]
        public IActionResult Get()
        {
            return Ok(OrderList.Orders);
        }
    }

İşte bu kadar 🙂 Dikkat ederseniz ‘OrdersController’ isimli controller sınıfındaki ‘Get’ fonksiyonu geriye kaynağı bizler için önemli olmayan bir order listesi döndürmektedir. Şimdi gelin bu yapılanmayı test edelim…

İlk request Asp.NET Core - ETag Nedir? ETag İle Response Caching İşlemi Nasıl Gerçekleştirilir?
API’a yapılan istek neticesinde, response header’ında ETag değeri döndürmektedir.
If-None-Match değeri ile birlikte request Asp.NET Core - ETag Nedir? ETag İle Response Caching İşlemi Nasıl Gerçekleştirilir?
İkinci request’te, ilk request’te elde edilen ETag değerini ‘If-None-Match’ header’ına parametre olarak verip gerçekleştiriyoruz. Status Code değeri 304(Not Modified) dönerek sunucuda ilgili değerin değişmediğini, client’ın var olan bellekteki datayla işlem yapmaya devam etmesi gerektiğini bildirmektedir.
Data değişikliğinden sonraki ilk request Asp.NET Core - ETag Nedir? ETag İle Response Caching İşlemi Nasıl Gerçekleştirilir?
Kaynaktaki veriler üzerinde oynadıktan sonra request header’ında ‘If-None-Match’ parametresi olduğu halde yapılan request neticesinde sunucuda ETag değeri eşleşmeyecek ve data’da değişiklik olduğu algılanarak 200 Status Code değeri ile client’a gönderilecektir.
Data değişikliğinden sonraki ikinci request Asp.NET Core - ETag Nedir? ETag İle Response Caching İşlemi Nasıl Gerçekleştirilir?
Yine ‘If-None-Match’e yeni gelen ETag değerini vererek istekte bulunuyoruz. Sonuç olarak 304 Status Code’u eşliğinde döngü devam etmektedir…

Angular’da Basit Bir ETag Algoritması

Olaki herhangi bir UI teknolojisinde ETag yapılanmasını kullanmanız gerekebilir. Böyle bir duruma istinaden Angular üzerinden aşağıda verilene benzer bir çalışmayı örnek alabilir ve uygulayabilirsiniz.

import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http';
import { Component } from '@angular/core';
import { EtagService } from './services/etag.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  constructor(
    private httpCLient: HttpClient,
    private etagService: EtagService
  ) {}

  getir() {
    this.httpCLient
      .get('https://localhost:5001/api/orders', {
        observe: 'response',
        headers:
          this.etagService.ETag != null
            ? new HttpHeaders({ 'If-None-Match': this.etagService.ETag })
            : null,
      })
      .subscribe((data) => {
        console.log(data.headers.get('etag'));
        this.etagService.ETag = data.headers.get('etag');
      });
  }
}
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class EtagService {
  ETag = "";
}

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Not : Örnek uygulamayı indirebilmek için buraya tıklayınız.

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

*