Django ve browser cache invalidation

Onceki yazimda HTTP caching mekanizmasinin nasil calistigindan birazcik deginmistim. Bu yazimda ise asagidaki sorunu nasil cozdugumu anlatmaya calisacagim.

Hem statik dosyalarimizin client-side’da en uzun sure cachelenebilmesini, hemde statik dosyalarimizda degisiklik olunca clientlarin dosyanin yeni veriyonunu indirmelerini nasil saglayabiliriz?

Ben bu sorunun cevabini Django uzerinden anlatacagim ama siz hangi programlama dili/framework kullaniyorsaniz onda da ayni sekil yapabilirsiniz.

Sunucumuzda style.css adinda bir css dosyamiz oldugu varsayalim. Biz bu dosyanin kullanicinin tarayicisinin cachinde maximum sure durmasini, dosyamizin degismesi durumunda da tarayicinin dosyamizin yeni versiyonunu indirmesini ve kullanmasini su sekilde sagalayabiliriz:

Sunucumuzda bulunan style.css dosyamizi style.<dosyanin_en_son_degistirildigi_timestamp>.js olarak kullaniciya gondererek saglayabiliriz. Bu sayede dosya degistiginde dosyanin degistirilme tarihide dolayisiyla dosya ismi otomatikmen degisecek ve tarayicinin cacheinde bu isimde bir dosya olmadigindan sunucuya yeni bir istek gonderek dosyanin yeni versiyonunu indirecektir.

Simdi bunu Django ile nasil yaptigimdan bahsetmek istiyorum. Yaptigim sey cok basit.

Normalde bir css dosyasini sayfamiza su sekilde ekliyoruz:

<link href="{% static 'css/style.css' %}" .... />

CSS dosyalarini bu sekilde sayfamiza eklemek yerine custom bir template tag olusturdum(Template tagin kodunu yazinin sonunda paylastim):

{% css 'css/style.css' %}

Bu template tagi verdigim pathe dosyanin degistirilme tarihini ekliyor ve su sekil bir cikti uretiyor.

<link href=".../css/style.min.1473070177.css" .... />

Css dosyamizin include edildigi sayfa client a gonderiliyor, client css dosyasini almak icin sunucuya tekrar istek yaptiginda nginx istegi karsiliyor ve istenilen dosyanin isminden timestampi cikarip orjinal dosyayi client a gonderiyor. Dosya her degistiginde timestamp degisecegi icin dosyalarimizin istedigimiz kadar uzun sure cache de kalmasini saglayabiliriz.

Bu sekilde hem statik dosyalarimizin isminde herhangi bir degisiklik yapmadan temiz bir sekilde cozmus oluyoruz.


Template taginin kodu (Django 1.10):

# common_tags.py

import os
import re

from django import template
from django.conf import settings
from django.contrib.staticfiles.storage import staticfiles_storage
from django.contrib.staticfiles import finders
from django.utils.safestring import mark_safe


__all__ = (
    'js',
    'css',
)

register = template.Library()


def _get_versioned_file_path(path):
    """
    Parametre olarak verilen pathe
    dosyanin degistirilme tarihini ekleyip donduruyor.
    Ornek girdi: css/style.css
    Return: css/style.12212312.css
    """

    file_path_regex = re.compile(r"^(.*)\.(.*?)$")
    mtime = int(os.path.getmtime(finders.find(path)))

    return file_path_regex.sub(r"\1.{}.\2".format(mtime), path)


@register.simple_tag
def js(path, **kwargs):
    """
    script tagi olusturan template tagi

    Ornek kullanim:
    {% js 'js/main.js' %}
    {% js 'js/vendors/foo.min.js'  versioning=False %}
    """
    versioning = kwargs.get('versioning', True)

    if settings.ASSET_VERSIONING and versioning:
        path = _get_versioned_file_path(path)

    result = '<script type="text/javascript" src="{}"></script>'.format(
        staticfiles_storage.url(path)
    )

    return mark_safe(result)


@register.simple_tag
def css(path, **kwargs):
    """
    Ornek kullanim:
    {% css 'css/style.css' %}
    {% css 'css/vendors/foo.min.css' versioning=False %}
    """
    versioning = kwargs.get('versioning', True)

    if settings.ASSET_VERSIONING and versioning:
        path = _get_versioned_file_path(path)

    result = '<link rel="stylesheet" href="{}" />'.format(
        staticfiles_storage.url(path)
    )

    return mark_safe(result)

Nginx rewrite rule:

location ~* \.[0-9]+\.(css|js)$ {
    rewrite (.*?)\.[0-9]+\.(css|js)$ $1.$2 last;
    access_log        off;
    expires           365d;
}


Bir kac django template tag ve filterlarinin bulundugu bir gist olusturmustum. Isterseniz onlarada bir goz atabilirsiniz.

comments powered by Disqus