Would dynamically created JavaScript files be cached? - php

So my application uses a LOT of js files. thats a lot of http requests. I decided to combine them dynamically at the server in packs of 3-4 files clubbed by functionality.
My client side request is:
...script type="text/javascript" src="http://mydomain.com/core-js.php" ...
My server side does:
--core-js.php--
header("Content-type: application/x-javascript");
include_once('file1.js');
include_once('file2.js');
include_once('file3.js');
include_once('file4.js');
I am setting a far future expire header on core-js.php. My question is, would core-js.php be cached at the client side? If it would be, could someone please explain how?
Thanks!

The client doesn't know or care that what got sent to it was satisfied by bringing together several files server-side. The client should cache it if the caching headers are correct. You'll want to check them carefully to be sure that your PHP install isn't sending other headers that conflict (Firefox+Firebug is good for this), since PHP pages tend to be used for dynamic stuff where you don't want caching.

Please see: http://www.jonasjohn.de/snippets/php/caching.htm, you have to check the incoming request headers to send the right response.
You can do something like below:
<?php
ob_start();
$filemtimes = array();
foreach(array('file1.js','file2.js') as $file)
{
include_once($file);
$filemtimes[]= filemtime($file);
}
$date = gmdate('D, d M Y H:i:s', max($filemtimes)).' GMT';
$length = ob_get_length();
$etag = md5($date.$lengte);
$headers = apache_request_headers();
if(!empty($headers['If-None-Match']) && !empty($headers['If-Modified-Since']))
{
if
(
$etag == md5($headers['If-Modified-Since'].$length)
)
{
ob_end_clean();
header("Content-type: application/x-javascript");
header('Last-Modified: '.$date."\r\n");
header('Expires: '.gmdate('D, d M Y H:i:s', (time()+3600)).' GMT'."\r\n");
header('Cache-Control: max-age=3600'."\r\n");
header('ETag: '.$headers['If-None-Match']."\r\n");
header('HTTP/1.1 304 Not Modified');
header('Connection: close');
exit;
}
}
header("Content-type: application/x-javascript");
header('Last-Modified: '.$date."\r\n");
header('Expires: '.gmdate('D, d M Y H:i:s', (time()+3600)).' GMT'."\r\n");
header('Cache-Control: max-age=3600'."\r\n");
header('ETag: '.$headers['If-None-Match']."\r\n");
header('Content-Length: '.$length."\r\n");
header('Accept-Ranges: bytes'."\r\n");
ob_end_flush();
exit;
?>
Your script will be cached. No data is send to the client. Server side the includes and modification calculation is done for every request. Maybe store etag and modification time in session or cookie to do the check before includes and calculations. Or check filesizes instead of includes.

The vast majority of browsers and caching proxies will respect the expiry header (if set).

Yes it will. The client doesn't know that the js file he's requesting is a bunch of other files chunked into one, he's just seeing one js file, the one he requested and it's telling him to cache it, core-js.php. As long as you don't change the name of the file (core-js.php) there should be no problem.
On another note, you should take a look at Minify http://code.google.com/p/minify/
You can merge and cache not only js but css in groups, basically what you're doing. I've been using it for a while with no problems and it's pretty nice.

Yes, but it's complicated. PHP by default adds a bunch of headers which prevent caching. You'll have to make sure you're removing all of them. Also, does your PHP script understand If-Modified-Since and If-None-Match headers? Do you even generate Last-Modified and ETag headers in the first place? This is tricky to get right, and why bother, when your webserver has all that built into it?
I'd do this differently. Make the request to core.js, not core.php. Of course, core.js does not exist, so .htaccess catches the request and directs it to index.php. Now index.php generates the required javascript and serves it to the client. It also creates the file core.js. Future requests for core.js will be handled by Apache as normal for static files, without going near PHP.
And if you want to be able to update the javascript, you can instead use URLs of the form last-modified-timestap.core.js. Changing the timestamp in the HTML will generate a new javascript file on the first request.
I do this for dynamically created CSS (the designer can edit CSS in the administration panel, with values saved into the database), and it works well.

Related

Is it possible to send a header from a PHP file, that does absolutely nothing

On a page where I offer music sample downloads, I have several <a> tags whose href points to a PHP file. Various data included as GET vars allow the proper file to be downloaded. Normally the PHP will respond with typical download headers followed by a readfile(). (the code for that is below, FYI). This results in a clean download (or download / play dialog box on some browsers). By "clean", I mean the download is completed with no disturbance in the visitors page.
However, in the unlikely event that the requested file is unavailable, I don't know what to do. I know it should not happen, but if it does I would like the download link to simply do NOTHING. Unfortunately since it is an <a> tag referencing a PHP file, doing nothing results in the browser clearing the page, with the URL of the PHP file in the address bar. Not a good visitor experience! So I'd like way to avoid disturbing the page and doing NOTHING if there is is an errant request. I'll use javascript to alert the visitor about what went wrong, but I can't have the errant file request clear the page!
I thought I'd had a solution by issuing a header('Location: #'); when the script detected an impossible file download. But after a few seconds the browser cleared the page and put up a message indicating the page "redirected you too many times." (indeed, my script log fills up with over 100 entries, even though i only clicked the tag once.)
So far the only solution I have that works (works in the sense of NOT disturbing the visitors page if an "unavailable" file is requested) is to point my download headers at a "dummy" file. An actual "silence.mp3" or "nosong.mp3" file. But is there a way to call a header() that does nothing to the calling page? Simply calling exit or exit() won't work (the visitor page is redirected a blank.)
Not that it matters, but this is the code I normally call in response to the d/l request...
function downloadFile($path) {
$path_parts = pathinfo($path);
$ext = strtolower($path_parts["extension"]); // don't need this.
$fsize =fileExists($path);
if ($fsize == 0)
{
header('Location: #'); // this doesn't work!!! (too many redirectcts)
exit;
}
//$dlname = $path_parts['filename'] . "." . strtolower($path_parts["extension"]);
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: filename=\"" . $path_parts["basename"]."\"");
header("Content-Type: application/x-file-to-save");
header("Content-Transfer-Encoding: binary");
if($fsize) header("Content-length: $fsize");
$bytesRead = readfile($path);
return $bytesRead;
}
If you are using HTTP/1.x with a standard anchor tag, without JavaScript or other client-side interception. An HTTP/1.0 204 No Content status header will cause the user-agent to simply seem like nothing happened when clicking a link that returns a 204 status header.
HTTP/1.0 204 No Content
The server has fulfilled the request but there is no new information
to send back. If the client is a user agent, it should not change its
document view from that which caused the request to be generated. This
response is primarily intended to allow input for scripts or other
actions to take place without causing a change to the user agent's
active document view. The response may include new metainformation in
the form of entity headers, which should apply to the document
currently in the user agent's active view.
Source: https://www.w3.org/Protocols/HTTP/1.0/spec.html#Code204
This is also compatible with the HTTP/1.1 protocol.
I recommend using output buffering to ensure no other content is being sent by your application by mistake. Additionally there should be no need to send a Content-Length header.
function downloadFile($path) {
if (!is_file($path) || !($fsize = filesize($path))) {
header('HTTP/1.0 204 No Content');
exit;
}
$path_parts = pathinfo($path);
header('Cache-Control: public');
header('Content-Description: File Transfer');
header('Content-Disposition: filename="' . $path_parts['basename'] . '"');
header('Content-Type: application/x-file-to-save');
header('Content-Transfer-Encoding: binary');
header('Content-length: ' . $fsize); //fsize already validated above.
return readfile($path);
}
Performing the file checks before creating the links is the simplest way to do this.
If I understand your request correctly you have files that you wish to allow a client to download, and links to PHP scripts that download certain files.
The problem with your implementation is that when the file is empty, the PHP script still must load and change the content of the clients page(from the action of loading the script), which is the incorrect behavior (correct being no action at all).
Since you are using tags on the main download page, really the only way to not change the content of the page in the case of a missing file is to compute the content of the tags in advance. With a simple PHP function you could check the contents of a list of files and their directories, and then generate links for the ones that exist, and blank links for the ones that do not.
Overall, I think separating the functionality of checking whether a file exists and actually downloading the file to a client is the only way to allow the functionality you desire.

How to update browser cache from PHP?

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.

javascript file not being cached?

I'm trying to optimize my web application and unfortunately have ended up with a javascript file size of around 450K - that too after compressing [it would take a while for me to redo the javascripting but until then I have to go live] - I initially had made a number of small javascript libraries to work upon. And what I do is I have a php file which includes all the javascript files and then I included my php file as below:
<script language="js/js.php"></script>
The thing is that I was hoping that my file would be cached upon the first load but it seems every time I refresh the page or come back to it the file is reloaded from the server - I checked this using firebug. Is there anything else that I must add to ensure that my file is cached on the user end.. or am I misunderstanding the idea of a cache here?
You'll need to set some headers in php to ensure the file is cached.
At the top of js.php put:
ob_start("ob_gzhandler");
$expires = 2678400; // 1 month in seconds
header("Pragma: public");
header("Cache-Control: maxage=".$expires);
header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$expires) . ' GMT');
That will add both basic caching + gzip compression on the fly.
Why not to leave it .js file and let web-server take care of caching?
Compression is not the thing you really need but Conditional Get is

Combining multiple CSS files

Right now I use a PHP script to pull together multiple CSS files into one script and then output them with a content-type of text/css.
The problem them with this is the browser wont cache the file.
Is there a better way of doing this?
Thanks
If you have to serve the CSS via PHP, you can force a cache header to be emitted, so the browser can cache the output if it so desires:
<?php
header('Cache-control: max-age=3600'); // cache for at least 1 hour
header('Content-type: text/css');
readfile('css1.css');
readfile('css2.css');
etc...
Why don't you just use #import in a global css file and link that into your html file?
see: http://www.cssnewbie.com/css-import-rule/
"Cascading style sheets" are so called before CSS files may include others. You can also specify several CSS files in your HTML file (using LINK) instead of including them inline.
Use these facilities and let your web server take care of sending the appropriate headers for client-side caching an handling of conditional HTTP requests.
I use the code posted bellow.
It follows Google's page speed recommendations.
Do notice that readfile is faster that include so should be used.
<?php
#$off = 0; # Set to a reasonable value later, say 3600 (1h);
$off = 604800; # Set to 1 week cache as suggested by google
$last_modified_time = filemtime('csscompressor.php');
$etag = md5_file('csscompressor.php');
ob_start("ob_gzhandler");
ob_start("compress");
header('Content-type: text/css; charset="utf-8"', true);
header("Cache-Control: private, x-gzip-ok=''");
header("Last-Modified: ".gmdate("D, d M Y H:i:s", $last_modified_time)." GMT");
header("Etag: $etag");
header("Expires: " . gmdate("D, d M Y H:i:s", time() + $off) . " GMT");
// load css files
readfile("global.css");
readfile('jquery-theme.css');
// ...
?>
You should also serve all CSS and JAVASCRIPT pages like this:
<script src="http://example.com/myjavascript.js?v=<?=$version=?>" ></script>
The $version variable is controlled by you. It should be set in a site-wide config file. Every time you push an update live you can just change the version on one place and everyone seeing the new content will just push it and not depend on cache.

writing dynamic headers

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.

Categories