My situation
I am currently using Cache Busting when I include CSS files like this:
echo "<link href='stylesheet.css?" . filemtime('stylesheet.css') . "' />"
My goal
Now I would like to do something similar for the images I am including inside my CSS file.
The problem
The problem is that I cannot use PHP inside my CSS files and I would rather keep my CSS files seperated.
My question
How can I add the filemtime() to the images inside my CSS files while keeping the files seperated?
Edit
I would like to use Far Future Expires headers to cache the files.
You could actually rename your css files as style.css.php and then use PHP inside them. As long as the post-processing result is in the proper CSS format, it should work. I've done it in the past. I'm not sure if it's necessary, but if that gives you problems, you can use a header('Content-type...') kind of thing and ensure it's sent as a CSS file.
To achieve cache-busting, the best way is to send the proper headers. Make sure Apache is configured to send a Expires: now header. So in a .htaccss file:
Header always set Cache-Control "no-store, no-cache, must-revalidate"
Header always set Expires "Thu, 01 Jan 1970 00:00:00 GMT"
That will always force no-caching of all content in its directory and any under.
However, if you wanted to conditionally cache, what I would suggest, is doing one of a few things.
Include a version number in the name of the CSS file. So you'd have a file that looks like mycss.1.css, mycss.2.css. This takes a little more work since you need to coordinate both filenames. But it's better since you aren't sending the files with PHP (no resource hit), you can use a CDN (even better) and you can still take advantage of far-future expires headers.
Set the Cache-Control: must-revalidate header and a proper E-Tag header so that all it needs to do is send a 304 Not Modified header if the content didn't change...
Related
There appears to be something bazaar I have just been sent.
There is one main CSS file that is actually a php file with a header to declare itself as CSS. It then includes other CSS also actually PHP files all of which also include that header.
header("Content-type: text/css; charset: UTF-8");
I can see why they have done this as they are passing PHP variables to the CSS files but suspect that that header only needs to be declared once. The site is causing a number of errors.
Would I be correct that a PHP file that declares that header writes a bit of CSS and then includes other PHP files that also contain CSS, that these other files do not need that header as well.
It sounds like you're correct, yes. You only want to send the headers one single time. Instead of having each included PHP file send the headers, I'd just have one send the headers (maybe call it css.php or styles.php or something), and then include in all the other ones that actually contain the CSS. I'd just use that main file for any logic that might be needed to decide what css needs to be included (if it's always the same, maybe it should be a static file anyway).
It'd probably be better to just have static CSS files anyway, to make your site faster and whatnot, but since that wasn't in your question, I assume you're aware of the drawbacks and for whatever reason need to do it this way.
I'm using Minify (https://code.google.com/p/minify/) to compress and combine ~30 css files and ~10 javascript files. I've created a group configuration for those files, which is basically an array with the filenames.
Works like a charm, except when one of the scripts is modified: browser cache is not timely update. I used to get a last modified timestamp for each file (using filemtime) to overrule browser caching:
$time = '?' . filemtime( $filename );
$output = '<link type="text/css" rel="stylesheet" href="'.$file_path.'?'.$time.'" />';
I could loop through all 40 files to get the latest timestamp, but that is a very ugly solution. Another way would be e.g. to have a crobjob check it and write the timestamp to a file, which I can then include.
Any other ways before I'm inventing the wheel again?
can you not just add:
src="path/to/file.css?v=<?php date(dmy); ?>"
Just get the modification time of the $file_path...? Personally, I have a much more reasonable number of files (two or three) and each one is individually cached with its mtime. Works beautifully.
I can suggest you a way that you need to define a rewrite rule and implement a handler for static file load. Let say it is assethandler.php, and your rewrite rule;
RewriteRule ^nocache/(.*?)$ assethandler.php?f=$1 [QSA,L]
And in php you can use Cache-Control and Expires for getting always latest file;
header("Cache-Control: no-cache, must-revalidate");
header("Expires: Mon, 01 Jul 1990 05:00:00 GMT"); // past date
readfile('path_to_static_files/' . $_GET['f']);
and your static file requests will be like;
<script src="nocache/your.js"/>
Simply, when you make a request to nocache/filename this will be handled as assethandler.php?f=filename and in this handler, cache always disabled and file content served as latest content
I have a PHP file get_css.php which generates CSS code more than 60 KB long. This code does not change very often. I want this code to be cached in user's browser.
Now, when i visit a HTML page several times which includes get_css.php url to fetch css, my browser is loading all CSS contents from the server each time i visit the page.
Browsers should get the contents from server only if the CSS code is changed on server side. If the css code is not changed, browser will use the css code from the browser cache.
I cannot use any PHP function which is not allowed in Server Safe Mode.
Is it possible? How can i achieve this?
You cannot force a client to revalidate its cache so easily.
Setting a variable query string to its resource won't play well with proxies, but seems to suffice with browsers. Browsers do tend to only redownload the css file if there's a query string change.
<link rel="stylesheet" type="text/css" href="/get_css.php?v=1.2.3">
Potentially, you could play with the naming of the CSS, such as add numbers, but this isn't a great alternative.
You cannot control browser behaviour from PHP, but you can use HTTP codes to tell the browser something.
If the CSS is not changed, just reply with a 304 Not Modified response code:
if ($css_has_not_changed && $browser_has_a_copy) {
http_response_code(304);
} else {
// regenerate CSS
}
This way, the browser will ask for the document (which you cannot control), but you tell him to use the cached copy.
Of course this needs testing, as I have now idea how it will work 'the first time' a browser requests the file (perhaps the request headers can tell you more). A quick firebug test reveals that Firefox requests Cache-Control: no-cache when it is requesting a fresh copy, and Cache-Control: max-age=0 when it has cache.
add normal GET parameter when you including get_css.php like so
<link rel="stylesheet" type="text/css" href="get_css.php?v=1">
Browser will think that it is new link and will load it again.
and in get_css.php use this to make browser cache data
<?php
header("Content-type: text/css");
header('Cache-Control: public');
header('Expires: ' . gmdate('D, d M Y H:i:s', strtotime('+1 year')) . ' GMT');
ob_start("ob_gzhandler");
//echo css here
The browser wants to cache your document by default, but you have to give it enough info to make that possible. One fairly easy way is to send the Last-Modified header, containing the date/time at which your script was last changed. You'll also need to handle the browser's "revalidation" request correctly by checking the incoming Last-Modified date, comparing it to the actual modified date of your script, and returning a 304 Not Modified response (with an empty response body), if the file is unchanged.
It's also a good idea to be sure that your server isn't "magically" sending any other "no-cache" directives. The easiest way to do this is to send a Cache-Control directive that tells the browser exactly what behavior you expect.
Here is a quick explanation of each Cache-Control option.
Something like the following should do the trick:
<?php
// this must be at the top of your file, no content can be output before it
$modified = filemtime(__FILE__);
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
$if_modified_since=strtotime($_SERVER["HTTP_IF_MODIFIED_SINCE"]);
if( $modified > $if_modified_since ) {
header('HTTP/1.0 304 Not Modified');
exit();
}
}
header('Cache-Control: must-revalidate');
header('Last-Modified: '.date("r",$modified));
// ... and the rest of your file goes here...
The above example was based heavily on the example, and writeup found here.
I am using gzip on my css to compress on page load. To do this I have made my css (stylesheet.css.php) file.
This is my PHP script included inside file :
<?php
ob_start ("ob_gzhandler");
header ("content-type: text/css; charset: UTF-8");
header ("cache-control: must-revalidate");
$offset = 60 * 60;
$expire = "expires: " . gmdate ("D, d M Y H:i:s", time() + $offset) . " GMT";
header ($expire);
?>
/*css stuff*/
I understand that the ($offset = 60 * 60;) is "sending an 'Expires' header, to set an age on how long our cached file will last. Here we set it to expire in one hour."
My problem is obviously if it expires every hour and I change a CSS setting it wont update until an hour, thus giving a user un-updated stylesheet? How do I change it to say update every one min so it fetches an updates stlyesheet every minute not hour? Would I just put ($offset = 60;) meaning 1 second?
My other problem is in internet explorer when I use HTTPS and say user hits back button IE gives message "do you want to view only the webpage content that was delivered securely" and you can click 'Yes' or 'No'. If you click 'No' then the CSS does not get loaded on that previous page. I am accessing my CSS using link href='/css/stylesheet.css' path format so cant see why its not secure?
Thanks
To shorten the time the clients wait before requesting the file again, you can shorten your 'offset' like you said. The unit the time() function uses is seconds, so '60' would mean one minute. If you update your css regularly, maybe you should not use the expires header at all to save you the hassle, since most clients automatically cache css files.
There are two solutions for the https problem. Either you check if the request comes in via http oder https and write the url for your css file accordingly, or you use just //folder/path/to/cssfile.php as the path to your css file. Note the 2 slashes and the absence of 'http'. This notation automatically gives you the right protocol, like here.
You can't do that.
Expire is meant to don't make an additional request by the client.
You will have to wait 1hour to be sure that all user cache are flushed.
The dirty way is to append a param whenever you change your css.
Something like:
style.css?param=123
What you are suggesting is a bad idea. The way you want to set it up will totally break all caching of your CSS file and have you regenerating it on the fly for every request.
It is definitely a best practice to serve gzipped files, the problems are the way you're doing it. First, hitting the PHP interpreter and gzipping on the fly on every request is very bad for performance and more than negates the benefit of serving a compressed file. Plain HTTP file requests are going to be answered 10x faster than something than hits PHP.
The other issue is that, yes, you can reduce the expires time to 60 seconds (effectively, to zero ... but as svanelten says, at that point you might as well not send the header at all). There's no problem with not sending an expires header (or a short one) if that's really what you want, but doing this in combination with dynamically generating the page is bad if the content isn't actually changing that often. This breaks If-Modified-Since, which browsers send to make conditional requests so that they don't have to redownload the file if it hasn't changed. You are not setting a Last-Modified time so your last-modified time is effectively always "right now."
There are three ways to fix these problems (and accomplish your original goal of serving a compressed CSS file faster, in a cache-friendly way):
Just enable gzip encoding in your web server's config and serve the CSS as a regular file. Then the web server handles everything. i.e. in Apache2, turn on mod_deflate.
The next easiest way is to gzip the file and let your web server serve it. That way, the web server takes care of sending Last-Modified (which will just be the actual file modification time). And, it will send HTTP/304 Not Modified in response to conditional requests, so browsers don't have to re-download the file.
For instance, if you are using Apache, you could just gzip the file and put in the same directory as your regular css file (so you have, for example, file.css and file.css.gz). Then put this into your Apache config or .htaccess:
<Files *.css.gz>
AddType "text/css" .gz
AddEncoding gzip .gz
</Files>
RewriteEngine On
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{REQUEST_FILENAME}.gz -f
RewriteRule ^(.*)$ $1.gz [QSA,L]
The last way (if you must use PHP for this) is to have PHP cache the gzipped file, send the Last-Modified header for when the file was actually last modified, and respond to If-Modified-Since and If-None-Match requests by actually checking these things and then returning a 304 or 200 response accordingly. I do not recommend this because it still hits the PHP interpreter, and you are basically re-implementing webserver functions in PHP, but I've done it in special cases. It's the worst solution but at least better than dynamically generating everything.
I have a php dynamically generated image which I need to write to file to call later. My problem is that I need this image to have appropriate expiration headers included in it. There are a massive number of these and their headers vary individually file-by-file making .htaccess controls not an option.
I can write expiration headers if I'm outputting the image directly to the browser with this:
header("Content-Type: image/jpeg");
header('Expires: "' . gmdate("D, d M Y H:i:s", $expirationDate) . '"');
imagepng($image, NULL);
Or I can write the image to a file to be used later with this:
imagepng($image, $filepath)
But I can't for the life of me figure out how to combine those two and write the image to a file while including its expiration headers. How would you go about writing an image file with an expires header?
I think your best bet it to server the file just as you are, something like:
header("Content-Type: image/jpeg");
header('Expires: "' . gmdate("D, d M Y H:i:s",
$expirationDate) . '"');
imagepng($image, NULL);
Sure you're using php to serve a static file, but the expire header is going to limit repeat requests.
Update: Since $image is a generated file, on the first request generate and save the image, then output it. On additional requests, just output the already generated image. Essentially the expire headers are controlling the browser's cache, while you need to implement some kind of caching on the server to avoid generating the same output multiple times.
So you're looking at two different kinds of caching. You can do them in the same script, with a combination of two scripts - really however you want.
Unless you can set a standard expire header with apache (which you say you can't, since it varies), I believe this is your best (if not only) choice.
Of course there is the convoluted and complex way:
Set up mod_rewrite to send requests for missing images to your php script.
Append some session id to the image request (so it's unique to the browser).
Have the php script send the expire header, and the image content.
Have the php script link the real static image to the session specific image name.
Or something like that. I'd just serve them all up using php.
Update: Or use mod_asis from VolkerK's great answer.
If you really want to store both the headers and the content in files on the server you could use mod_asis:
In the server configuration file, associate files with the send-as-is handler e.g.
AddHandler send-as-is asis
The contents of any file with a .asis extension will then be sent by Apache to the client with almost no changes. In particular, HTTP headers are derived from the file itself according to mod_cgi rules, so an asis file must include valid headers, and may also use the CGI Status: header to determine the HTTP response code.
Your php script then would write both the headers and the content to files that are handled as send-as-is by the apache webserver.
Perhaps all you have to do is exactly ...nothing, except writing the image data to the disc.
Depending on the webserver you're using some caching mechanisms work out of the box for static files (which you would create with the php script).
If you're using apache's httpd take a look at http://httpd.apache.org/docs/2.2/mod/core.html#fileetag and http://httpd.apache.org/docs/2.2/caching.html. By default httpd will also send a last-modified header and it supports If-Modified-Since request headers.
When your php script changes the image files the ETag changes as well and/or the If-Modified-Since condition would be met and the httpd sends the data. Otherwise it would only send a response saying "nothing has changed" to the client.