CSS caching theory anyone want to contribute?

Jazajay

Active Member
Right I want to start off by saying that I have not written any code yet but will do hopefully later today when I get up. Now I currently cache my CSS files for like 10 years into the future saves on repeat users having to download them next time and this reduces http headers and thus speeds the page download up. Now you run into a problem and that is if you make amendments to that file anyone who viewed that file before will still see the old copy.

Now the way I get around this currently is with a technique known as file versioning, that is you make 1 amendment and you change the file name, this tricks the browser into thinking it is a new file, because well it is, and the new updates are then added. However as with everything I reckon I can do a better job. :p

So here's my theory/problem and I'll post my code written off the cuff it may not work but I don't see why it wont. I will warn you now it will get geeky.

So I was adding a hash to a program I have recently written and it occurred to me the best thing about a hash is if 1 character is different the hash will be changed. I guess some of you see where I am going with this.

So we get the CSS file and hash it, we then set a cookie of the hash on the users computer. We then test for the cookie and get the hash from it. If the hash matches the CSS it hasn't changed if it doesn't or if the cookie is not present, incase the user wiped their cookies, we add the cache control PHP header telling it to revalidate it's copy thus no worry of the user having an out of date sheet, I don't have to spend time changing and uploading multiple files then.

So off the top of my head something like this.

1. <?php
2. $hash=hash_file('md5', main.css');
3. if(!isset($_COOKIE['cache']) || $hash!==$_COOKIE['cache']){
4. setcookie("cache",$hash,time()+315360000)?>
5. <link type="text/css" href="css/main.css" />
6. <?php }?>

Now how to get it to revalidate is currently where my theory gets a tad unstuck as I'm not sure about this bit.
Mmm...okay what about wacking in a bit of mod_rewrite and getting a tad clever.

So we have the above code.
We then write a .htaccess rewrite rule changing the file but by adding a get variable that we don't do anything with we then save that in the cookie for future use.

So ~

1. <?php
2. $hash=hash_file('md5', main.css');
3. if(!isset($_COOKIE['cache']) || $hash!==$_COOKIE['cache']){
4. setcookie("cache",$hash,time()+315360000);
5. if(isset($_COOKIE['num']))
6. {
7. $newNum=$_COOKIE['num']++;
8. }else{
9. $newNum=0;
10. }?>
11. <link type="text/css" href="css/main.<?php echo $newNum;?>.css" />
12. <?php setcookie("num",$newNum,time()+315360000);
13. }else{?>
14. <link type="text/css" href="css/main.<?php echo $_COOKIE['num'];?>.css" />
15. <?php }?>

Then with a bit of mod rewrite ~
RewriteEngine On
RewriteRule ^css/main.(.*).css$ css/main.css.php?num=1

Then in the CSS sheet which would now be a PHP file we tell it to parse it as a CSS file so at the top of the file we write ~
<?php header ("content-type: text/css; charset: UTF-8")?>

And that should then request a "new" file which is in fact the same file with a get variable added to it but the updates should then take effect.

So what do ppl think?

Bare in mind this is just a theory at the mo and I haven't tested my code yet.
Any improvements, anything I could do better, wont it work, if not why not?

Pretty much feedback wanted. :)

Jaz

Key:
Red ~ PHP
Blue ~ XHTML
Brown ~ Mod_Rewrite
 
That's a very clever automatic solution but I don't think making the .css a php file is necessary. Can you not just add a variable to the end of the .css like gloabl.css?v=1
 
You could be right, I use PHP in every page I build so to me variables go on PHP pages, but I don't suppose they have to. Mmm...:)

Okay so we can take out the header in the CSS file. I'm going to try it now and let you know if it works it should because even if they don't enable cookies they should get the "new" CSS sheet.

Cheers for the feedback Ben much appreciated fella. :)
 
Had any luck? I'm thinking of using this for my new site... although I might be getting ahead of myself there, I haven't even finished the design yet.
 
Almost got stuck but yeah literally just got it a min ago. Right here goes ~
Cookies are obviously a header so they have to be set before output and I had to change a few things as they where bollox and TBH I all most gave up till I had an idea. At 1 point though I had triple the code then realized why I was being an idiot. :)

So here's the page now ~

<?php
$hash=hash_file("md5","css/main.css");
if(!isset($_COOKIE["cache"]) || $hash!==$_COOKIE["cache"]){
setcookie("cache",$hash);
$newNum=rand(0,350000000000);
setcookie("num",$newNum);
}
?>
<doctyp.../>
<html>
<head>
<title></title>

<?php if(!isset($_COOKIE['cache']) || $hash!==$_COOKIE['cache']){?>
<link href="css/main.<?php echo $newNum;?>.css" rel="stylesheet" type="text/css" media="screen" />
<?php }else{?>
<link href="css/main.<?php echo $_COOKIE['num'];?>.css" rel="stylesheet" type="text/css" media="screen" />
<?php }?>
</head>
<body>


</body>
</html>

And that's it.

Experiment walk through.
Right we start by hashing the file, then testing to see if the cookie with the hash is not set or if the hash does not equal the hash we stored in the cookie.

This is important because if the cookie is not set either this is their first time with us or they have removed the cookie so we therefore need to give our CSS sheet a unquie number just incase they removed our cookie. If the hash does not equal the hash stored in the cache cookie this means 1 of 2 things.

Firstly they have already been to our site before, and secondly the CSS file has changed in the mean time.

So we then want to set a cookie with the hash of our current CSS sheet in in case they don't have a cookie with it in, we can then test this next time.

After that we want to generate a number for our CSS sheet, now I ran into issues by setting this back to 0, because if they have already been with us they already have a CSS sheet called main.0.css saved locally with old rules in, this will then load giving them old rules and not what we want. So instead we set it to a huge number, which the server will generate between 0 and 350000000000 so the chances of them getting the same number as the number they already have are so remote I wouldn't even want to calculate the chances of them getting the same file twice. Bare in mind this will only happen if the CSS sheet gets changed or they have deleted the cookie in the mean time as well not every time the page loads making the % of them getting the same number even more ridiculously remote.

We then set a second cookie with the new number in this then becomes our cached version of our CSS file.

We then start the XHTML page.

When we get to our CSS sheets we want to test again to see if the cookie is set or the hash equals what was set in the cookie, same reasons as above, if it doesn't or there is no cookie present we give the CSS sheet the new huge random number.

If the cookie is present and the hash within it equals the current hash the CSS file hasn't changed so we load the cached CSS sheet.

So now we add the Mod_Rewrite, Ben it doesn't matter about the variable as we are not using it, therefore adding it to the CSS file via Mod_Rewrite is redundant.

RewriteEngine On
RewriteRule css/main.(.*).css$ css/main.css [L]

Experiment was conducted by:

I tested this using a control page just linking to the main file and this. When I made a change the control stayed as it was before and this changed to the new version. Open up another tab and load the page again, no refresh, and the control was still the old CSS sheet and the new 1 stayed the same as the new rules. I then did this a third time and the control was the first sheet, this file was the third change. I've tested the output and the output number only changes when the hash changes which only changes when the files been modified so it works.

Now anyone want to give me feedback on this version.
Any obvious problems I've overlooked, what do you think?

Cheers geeky Jaz

Key:
Red ~ PHP
Blue~ XHTML
Brown
~ Mod_Rewrite
 
This has been bugging me a bit. I had that feely that there was a simpiler way to do this and I think I might have found it. Check this out and tell me what you think.

So we hash the css file and cut out the middle of the string.
<?php
$hash = hash_file('md5','css/main.css');
$hash = substr($hash, 12, 8);
?>


Then all we do is pass the 8 digit string to the end of the css file as a variable.
<link rel="stylesheet" media="all" type="text/css" href="css/main.css<?php echo '?='.$hash; ?>" />

This will create a .css file like css/main.css?=f2ab8ff5
You could of course leave the hole 32 character hash but I thought that was messy. I'm no expert at md5 hash's or probability but I think extracting 8 digits from the middle of the hash is going to give a reliable difference each time the .css is changed.

I've done some testing and it seems to work beautifully for me.
 
Why Benjamin that is a beautiful piece of code, liking it. :clap:

I would make it a 15 digit section as that way the odds are greater you don't pull out the same digits, but otherwise very nice and loving the simplicity. :)

I'll test it first obviously but I'll implement it over the next few days on a few sites I think, very nice.

Cheers for your input into the problem very much appreciated.
 
Could you not just get the last modified time of the CSS file and then append it to the end of the filename? For example:

HTML:
<link rel="/css/screen.css?v=20091221" ...

If you're working on multiple revisions per day you could always use a UNIX time stamp instead? That's how I got around the problem whilst working on an enterprise-sized site this autumn/winter where I was writing multiple CSS file revisions per day.

I even wrote a function for the above process into the application's framework to allow it to be used for CSS files, image files, JavaScript files—you get the picture.
 
I like that. Although it would be best to use a unix timestamp even if you updated your css every few months. Because if you changed the css in the morning then your users who had the css cached would be using the old version until the end of the day.

I've also created a helper function to append the css file, but with the hash method. I think I might switch to this way though.

Cheers

EDIT: Just done some testing and (on my localhost) creating a timestamp with the modified time of the css file actually increased the memory load on the server alot more than using the hash method. Obviously alot more testing on different css file sizes and amount of attached sheets might say different, but im not THAT anal :)

I think I'll stick with the hash method for now.
 
Back
Top