Javascript és CSS fájlok automatikus frissítése
Egy folyamatosan zajló webfejlesztés velejárója, hogy a böngészők agresszív cache techinkái minden frissítés után törött oldalakat eredményeznek. Képzeljük csak el, hogy a frissített tartalom töltődik le a látogató böngészőjébe, viszont ha talál cache-elt stíluslapot (.css), vagy külső javascript (.js) fájlt, a böngésző a régi, letárolt változatot fogja használni, anélkül, hogy ellenőrizné ennek frissülését.
A megoldás igen egyszerű: egy Reload, vagy SHIFT + Reload megoldja a problémát, ezt viszont nem írhatjuk ki az oldalra.
Verzió ellenőrzés
Egy frissült verziójú javascript ellenőrzése igen egyszerű. Mivel egy új script beépítése általában a html tartalom frissülésével együtt jár, egy változó párral könnyen ellenőrizhetjük, hogy a látogató gépén is a legfrissebb verzió van-e. Nézzük a képzeletbeli menu.js tartalmát:
menuVersion = 3;
function menu () {
...
}
A hozzátartozó oldal (inline scriptben) leellenőrizheti, hogy a külső .js fájlokból érkező verziók a legújabbak-e (ehhez az oldalnak tartalmaznia kell a megfelelő verziók számát).
Ez a módszer ugyan működik, de legfeljebb a felhasználó figyelmeztetésére jó, ami nem a legelegánsabb megoldás.
Szelektív cache tiltás (mod_expires)
Ha Apache webszervert hasznáunk (és miért ne használnánk) lehetőségünk van egy kiváló direktíva, az ExpiresByType használatára. Ennek segítségével minden MIME tipusnak beállíthatunk egy saját elévülési időt, aminél tovább a böngésző nem fogja már tárolni. (A következő részletet egy .htaccess fájlba helyezve, vagy a szerver konfiguráció <Directory> direktívájában hazsnálhatjuk.) Nézzünk egy példát:
ExpiresActive on
ExpiresByType application/x-javascript A3600
ExpiresByType text/css A86400
Az első sor ExpiresActive aktiválja a szolgáltatást. Majd beállítunk a javascript fájloknak egy órás (3600 másodperc) elévülési időt. (Az A kóddal jelezzük, hogy az aktuális időpillanathoz képest adtuk meg az időt.) Ez gyakorlatilag azt jelenti, minden látogatáskor egy alkalommal letöltődik a külső szkriptünk (mivel egy látogatás ritkán tart ennél tovább). Egy statikus fájl elküldése a szerver számára nem olyan nagy munka, úgyhogy ezt egy józan kompromisszumnak tartom.
A css fájlokkal nem voltam ilyen szigorú. Én úgy érzem egy rossz css nincs olyan végzetes hatással egy oldalra, mint egy rossz javascript. It 1 napot (86400 mp) adok meg. Természetesen ez oldalfüggő, a fejlesztő belátására van bízva.
Nagyon különleges helyzetekben teljesen tilthatjuk is a cache működését (0 másodperc). De erre inkább nem is írok példát. :)
Verzió az URI-ba ágyazva
A legtisztább megoldás minden frissüléskor új nevet adni a scriptnek, ilyenkor a böngésző biztosan a megfelelő fájlt használja. (Megtartjuk a cache funkciót úgy, hogy korábbi cache-elte változatok nem zavarnak be.) Ezt megtehetjük egy növekvő számmal a fájlnév után, vagy akár dátummal is. Példa mindkettőre:
<script type="text/javascript" src="menu_3.js"></script>
<script type="text/javascript" src="menu_20060523.js"></script>
A megoldás különlegessége, hogy megtarthatjuk a különböző verziókat egymás mellett, így egy cache-elt oldal (pl. google tárlolt változat) is működőképes marad. Ennek ellenére én a következő megoldásra eskölszöm:
Verziókövetés hamis URI (mod_rewrite) és PHP segítségével
A fantasztikus mod_rewrite, minden URI-k Szent Grálja ebben az esetben is jó szolgálatot tesz. A megoldáshoz PHP is szükséges.
Az elv ugyanaz, mint az előző (dátumos) példában, itt viszont nem kell átneveznünk a fájlt. Az aktuális dátumot (és időt) a PHP fűzi a fájlnév végére, az így keletkezett URI-k (URL-ek) feloldását (mivel nemlézető fájlokra mutatnak) pedig a mod_rewrite végzi.
Először a beillesztés (a példában éppen javascript, de külső css beillesztésére ugyanígy használható):
<script type="text/javascript" src="<? add_timestamp ('menu.js'); ?>"></script>
Az ide vágó php függvény:
function add_timestamp ($uri) {
if (substr ($uri, 0, 1) == "/") {
// abszolút URI
$fname = $_SERVER["DOCUMENT_ROOT"] . $uri;
} else {
// relatív URI
$fname = $uri;
}
@$ftime = filectime ($fname);
echo ($uri . '.' . $ftime);
}
A függvény maga igen egyszerű. Levizsgáljuk, hogy relaítív vagy abszolút URI-t kaptunk-e. Relatív hivatkozás esetén feltételezzük, hogy php-ben (cd függvény) nem változtattuk meg az aktuális könyvtárat (vagy visszatértünk oda). Aboszolút hivatkozás esetén a web alapkönyvtárát (az Apache DOCUMENT_ROOT-ját) a hivatkozás elé helyezzük.
Ha megkaptuk a fájl helyét az aktuális fájlrendszerben lekérdezzük az utolsó módosítás idejét. Nem létező file esetén a PHP egy hibát adna, ezt elnyomjuk a sor elején látható @ (at) karakterrel. Egy unix timestamp formátumú azámot kapunk, ez az 1970 január elseje óta eltelt másodpercek száma, egy 9 vagy 10 jegyű szám (2001.09.09-én léptük át a milliárd másodpercet, úgyhogy az ezutániak lesznek a 10 számjegyűek).
Ennek eredménye egy URI (URL), ami automatikusan változik a fájl minden módosulásával:
<script type="text/javascript" src="menu.js.1148404454"></script>
A PHP megtette a dolgát, most jöjjön a mod_rewrite. A mod_rewrite lehetőséget ad, hogy bekapcsolódjunk a folymatba, amikor az Apache az URI feldolgozását végzi, és néhány alap műveletet elvégezzünk vele. A következő kódrészlet szintén egy .htaccess fájlba, vagy egy directory blokkba helyezve működik:
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} ^(.*)\.[0-9]{9,10}$
RewriteCond %1 -f
RewriteRule ^(.*)\.[0-9]+$ $1 [L]
Az első sor aktiválja a modult. A RewriteCond egy előfeltételt jelent, a RewriteRule direktívának. Alapértelmezetten ÉS (AND) kapcsolat működik közöttük. Az egész modul lekét RegEx (Regular Expression - szabányos kifejezések) feldolgozása képezi. Ezek azok a furcsa operátorok a negyedik és hatodik sorban. Az egész szabány pedig kötülbelül így olvasható:
HA a kért URL nem létező fájl ÉS a kért URL nem létező könyvtár ÉS a kért URL vége: egy pont, majd min. 9 max. 10 számjegy ÉS a pont és a számjegyek levágása után létező fájlt kapunk AKKOR vágd le a pontot és a számjegyeket, majd azonnal dolgozd fel a címet (ne futtass további rewrite szabályokat).
Az utolsó (RewriteRule) sor önmagában is elég lenne a működéshez. A feltételek (RewriteCond) azért vannak, hogy csak megfelelő esetben fusson le a rewrite, és ha pl. létezik olyan fájl, ami ilyen furcsán néz ki, és sok számjegyre végződik, akkor azok végéről ne vágjuk le a számokat.
Egyszerüsítsünk: versziózás a query stringben
Miután a weblabor fórumában meghánytuk-vetettük a dolgot, felmerült egy egyszerübb megolás is. A böngészők jól reagáltak a tesztekre, úgyhogy már használom is egy éles helyzetben. Köszönet az ötletért!
Tulajdonképpen az előző módszerről van szó csak a RegEx nélkül. A lényeg, hogy a menu.js.1148404454 típusú virtuáls filenevek helyett menu.js?1148404454 tipusú URL-eket is generálhatunk. Ez minden szempontból az előzőhöz hasonlóan viselkedik, előnye, hogy a webszerver rewrite nélkül is megtalálja és kiszolgálja a fájlt, amire hivatkozunk. A módosításkor változó query string (?...) pedig biztosítja, hogy a böngészők ilyenkor ne használják az elavult cache-es változatot. Az erre módosított PHP függvény:
function add_timestamp ($uri) {
if (substr ($uri, 0, 1) == "/") {
// abszolút URI
$fname = $_SERVER["DOCUMENT_ROOT"] . $uri;
} else {
// relatív URI
$fname = $uri;
}
@$ftime = filectime ($fname);
echo ($uri . '?' . $ftime);
}
Módosított konklúzió
A helyzet válogatja mikor melyik a legcélravezetőbb módszer, és néha-néha lehet a rewrite-nál egyszerübb megoldásokat is találni.