İçeriğe geç

Django Middlewares

Middleware nedir?

Middlewareler, Django dökümantasyonunda belirtildiği üzere request-response döngüsünü mercek altına alarak, gelen isteği veya cevabı değişime uğratabilen, koşullu durumlarda uygulama davranışını yönetmemizi sağlayan low level bir eklenti sistemidir.

Her middleware belirli bir işlemi yerine getirmekle yükümlüdür. Örnek verecek olur isek, Django’nun yerleşik olarak sağladığı AuthenticationMiddleware middlewarei gelen istekleri sistemdeki kullanıcılar ile ilişkilendirmekle yükümlüdür.

Çalışma sırası ve mantığı.

Django gerekli kontroller ve ilişkilendirmeler adına bir çok middleware’e sahiptir. settings dosyasında MIDDLEWARE değişkeninde tanımlanırlar. Tek request-response döngüsünde 2 defa olmak üzere çalışırlar. Çalışma sıralaması ilk olarak yukarıdan aşağı, daha sonra ise aşağıdan yukarı şekildedir.

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

django-middleware-diagramGörsel kaynağı.

Yukarıdaki görsel sıralamayı görsel olarak canlandırmakta.

Not olarak, middlewarelerin hepsi global olarak kayıt edilir. Yani, gelen her istek ve dönen her cevap bu katmanları geçmek zorundadır.

Kullanım amaçları.

1. Filtreleme.

Middlewareler gelen isteği filtreleme adına kullanılabilir. Örnek verecek olursak gelen istek belirlediğimiz originlerden gelmiyor veya belirli güvenlik kurallarına uymuyor filtreleyerek, ilerlemesini durdurabilir ve istemciye bir hata dönebiliriz. Django CSRF middleware bir diğer güzel örnektir. Hassas data gönderme işlemlerinde, CSRF token payload da sağlanmamış ise isteği geçersiz kılabiliriz.

2. İstek veya cevaba yeni parametreler ekleme.

Yukarıda da bahsedildiği üzere AuthenticationMiddleware bizim için request nesnesine, istek yapan kullancıyı bağlar ve view ya da templateda kullanmamıza olanak verir. Başka bir örnek olarak ise LocaleMiddleware verilebilir. Eğer uygulamanız birden fazla dilde hizmet veriyor ise LocaleMiddleware istek yapan kullanıcının belirli bir algoritma ile istek yaptığı dili bulur ve dönülecek olan cevabı kullanıcı dil tercihlerine göre kişiselleştirerek döner.

3. Kolay debugging.

Uygulama debug etmek çoğunlukla geliştirmekten çok daha fazla zaman alabilir. (Tabii ki hiçbirimiz bug yazmayız. Her şey feature’dur 🙂 ) Örnek verecek olursak DB seviyesinde multi tenant bir uygulama geliştiriyor iseniz bazen hangi şemada olduğunuzu debug etmek zorlaşabilir. Geliştirme ortamında kullanılmak üzere o anda bağlı olan şemayı geliştirme sunucusuna loglayabilirsiniz. Veya hatalı çalışan bir viewınızı ürettiği hata ile birlikte uzak bir log sunucusuna anlık yollayarak bug-free bir ortam için güzel adımlar atabilirsiniz.

4. Analiz vs.

Her middleware doğrudan istek veya cevap manipüle etmeyebilir. Fakat size daha sonrasında kullanmanız üzere, sistematik olarak bir bilgi birikimi sağlayabilir. Örneğin uygulamanızın hangi saat dilimlerinde daha aktif olduğu, hangi bölgelerden daha fazla kullanıldığı gibi işlemleri kayıt altına alarak, analiz sonucunda çeşitli aksiyonlar alabilirsiniz.

4 ana başlıkta toplamaya çalıştım fakat bu çok daha fazla arttırılabilir. Genel request-response şablonunu ilgilendiren durumlarda bunu nerede yöneteceğimizi artık biliyoruz. O zaman biraz pratik yapalım!

Middleware oluşturmak.

Django middleware’leri belirli metodları olan birer sınıftır. Ve aşağıdaki gibi tanımlanır. Herhangi bir pathde yaşayabilir.

class CustomMiddleware:

    def _init_(self, get_response):
        # One time confirugation and initilaztion.
        self.get_response = get_response

    def _call_(self, request):

       # Code to be executed for each request before
       # the view (and later middleware) are called.

        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        # This block is executed before the view is executed

    def process_exception(self, request, exception):
        # If the exception occured, this block executed.

    def process_template_response(self, request, response):
        # If the response rendered a template, this code block executed..
        return response

Gerekli metodlar.

 

__init__

İlk metot olan __init__ metodu server ayağa kalktığı ilk anda tek sefere mahsus olmak üzere çalışır. Burada genelde middleware içinde kullanılacak olan bir takım işlemler yürütülür. Zorunlu bir parametre olan get_response metodunu kabul eder. Bu parametre middleware’e Django tarafından sağlanır. Karşılığı ise bir view(listedeki son eleman ise) ya da middleware olabilir. Çalışan middleware kendisinden sonra gelecek olan ile ilgilenmez. Zinciri devam ettirir.

__call__

Bu metod middleware’i çağrılabilir hale getirir. Middleware’de yapılacak olan genel işlemleri barındırır. Middleware’lerin bir istekte iki defa çalıştığından bahsetmiştik. response öncesi ve sonrasına gerekli olan işlemleri yerleştirebilirsiniz. Her yeni istekte bu blok tekrar yürütülür.


Opsiyonel yardımcı metodlar.

process_view

Bu metod yardımı ile çalışacak olan view çalışmadan hemen önce araya girerek, view argümanlarını, request argümanlarını değiştirebilir veya yenilerini ekleyebiliriz. Veya direkt bir cevap dönebiliriz. None dönmemiz durumunda Django isteği viewda işlemeye devam edecektir.

process_exception

Django view’ında yakalanmayan bir hata olması durumunda bu metod çalıştırılır. process_view metodunda da geçerli olduğu üzere None ya da bir HttpResponse dönebilir.

process_template

View işlendi ve bir template render edileceği anda araya girmenize olanak sağlar. Context datalarını veya bir takım template işlemlerini yapabilirsiniz.


Pratik zamanı.

Bir middleware yazalım. Sistem ayağa kalktıktan sonra tüm istekleri ve hataları saysın. Eğer karşılayamadığımız bir hata var ise kullanıcıya bir problemle karşılaştığımız ve çözmeye çalıştığımız bilgisini versin.

Basit bir view yazarak başlayalım.

from django.http import JsonResponse
def index(request):
    data = {
        "message": "you reach the index"
    }
    return JsonResponse(data)

RequestCounter.

class RequestCounter:
    def __init__(self, get_response):
        print(f"{self.__class__.__name__} initalized.")
        self.get_response = get_response
        self.request_count = 0
        self.exception_count = 0

    def __call__(self, request, *args, **kwargs):
        self.request_count += 1
        print(f'Request count {self.request_count}')
        response = self.get_response(request)
        return response

    def process_exception(self, request, exception):
        self.exception_count += 1
        return JsonResponse({"error": "We detected a problem and are working on it"}, status=500)

Kayıt edelim.

MIDDLEWARE = [

    ## builtin middlewares
    "app.middleware.RequestCounter",
]

Middlewareimizi en son sıraya ekledik. Server otomatik olarak başlatıldığında RequestCounter initalized. yazısını gördük. Fakat bu örnek için geçerli bir yöntem olsa da middleware sıralaması önemlidir. Eğer yazacağınız middleware diğerleri sonucunda üretilen bir takım çıktıları kullanıyorsa (örneğin kullanıcıya bağlı bir middleware) sıralama olarak bağımlı olduğu middlewarein altında konumlandırılmalı. Genellikle güvenlik middlewareleri ilk sıralara yerleştirilir fakat kullanım durumlarına göre bu yaklaşım da değişkenlik gösterebilir. Doğru sıralama sağlanmaz ise beklenmedik hatalarla karşılaşılabilir. Detaylı bilgi ve güvenli geliştirme için dökümantasyon burada 🙂

Test edelim.

Bir istek atalım.

RequestCounter initalized.
Request count 1
[23/Nov/2020 00:06:27] "GET /example/ HTTP/1.1" 200 34

Daha sonra viewa öngörülememiş bir hata ekleyelim ve isteği tekrarlayalım.

def index(request):
    data = {
        "message": "you reach the index"
    }
    1/0
    return JsonResponse(data)
$ http --body localhost:8000/example/
{
    "error": "We detected a problem and are working on it"
}

Her şey çalışıyor gözüküyor.


Son sözler.

Umarım bu yazı sizin için faydalı olmuş ve kafanızda bir şeyleri canlandırmıştır. Daha sağlam bir sistem için middlewareler çok önemli bir konu. Artık nasıl bir middleware oluşturacağınızı ve neyi nerede konfigüre etmeniz gerektiğini bildiğinizi umuyorum 🙂

Daha detaylı bilgi için dökümantasyon linki.

Django Request-Response döngüsünden bahsettiğim bir diğer blog için ise link burada.

İyi günler!

 

Tarih:Blog

İlk Yorumu Siz Yapın

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.

Göster
Gizle