On my web site, while the page is loaded, some javascript detects the screen resolution and then sets the body's background to be a url, using this code:
var url = "http://site.com/get_background/" + width + "/" + height;
var body=document.getElementsByTagName('body')[0];
body.style.background = "#000000 url(" + url + ") fixed top center";
At the url get_background/, an image is served using the following PHP code:
$etag = md5_file($image);
$lastModified = gmdate('D, d M Y H:i:s', filemtime($image)) . ' GMT';
header("Content-type: $mime");
header("ETag: \"{$etag}\"");
header("Last-Modified: $lastModified");
header('Cache-Control: max-age=28800');
$image = readfile($image);
imagejpeg($image);
imagejpeg($image);
In firefox, this all works as expected. However in Chrome, each time a page on this site loads, the image seems to get loaded once again rather than just being cached and served.
If I move the code for setting the background to an external css file, then it works as expected in chrome, however if I put it in javascript, then it seems to make Chrome refresh the image.
What should I do about it? As the screen resolution needs to be detected via javascript, therefore the code must be put in the JS. Is there a way to force chrome to cache the image even though its loaded via javascript?
To successfully cache the response you need to set Expires header in the initial response and on next responses you need to check existence of HTTP_IF_MODIFIED_SINCE request header.
And if it exists - you need to respond back with 304
More details may be found at http://dtbaker.com.au/random-bits/how-to-cache-images-generated-by-php.html
Related
How Wrapper class works
I created php class and now I am able to do this to put together all my css,js .It works like this Wrap::set(array(file.css,file2.css,file3.css)) then I wrote wrap::call_path and it begin to generate url like this www.example.com/wrap_contects?path[]=file.css&path[]=file2.css&path[]=file3.css
then in the server side I get all paths with in the array then I tried file_get_content for each of them,after that I wrote echo all results.It put together all CSS,if I wrote JS it will be JS.
Problem
Problem is browser is not cache this url.
Note
I consider all security issues,I created token to send wrap_content.php to prevent file get content to work for any file that user request
I solved this problem,hope it will help someone who has same problem to solve this problem in the future
I added these ones to wrap_contents.php:
$cache_length=60*24*30;
$cache_expire_date = gmdate("D, d M Y H:i:s", time() + $cache_length);
header("Expires: $cache_expire_date");
header("Pragma: cache");
header("Cache-Control: max-age=2592000");
(if css) header("Content-type: text/css");
(if js) header('Content-Type: application/javascript');
This created css,js simulator on the response with php file.
On an HTML page constructed using php + jsquery + javascript (e.g. index.php), a video tag has a source that is another php page, with a GET field specifying which video to load (e.g. "getfile.php?file=111").
Buttons switch which video is playing; e.g. javascript
var video = document.getElementById('flyover');
var source = video.getElementsByTagName('source')[0];
source.setAttribute('src', "getfile.php?file=222");
getfile.php emits HTTP headers, then fpassthru of file contents.
...
header('Content-Type: video/mp4');
header('Content-Disposition: attachment; filename='.basename($file->FileName));
header('Content-Transfer-Encoding: binary');
$seconds_to_keep = ...
header ("Expires: " . gmdate("D, d M Y H:i:s", time() + $seconds_to_keep) . " GMT");
header('Cache-Control: public, max-age=' . $seconds_to_keep);
header('Content-Length: ' . filesize($filename));
fpassthru($fp);
exit;
Fiddler proxy used to confirm headers:
# Result Protocol Host URL Body Caching Content-Type
47 200 HTTP ... /getfile.php?file=2639 10,113 public, max-age=31536000; Expires: Thu, 06 Aug 2015 20:20:30 GMT video/mp4
Test actions:
Load page
Wait for video #1 to finish playing (And Fiddler updates Caching info from "-1" to "max-age / Expires" details)
Push button for video #2
Wait for video #2 to finish playing (And Fiddler updates Caching info)
Push button for video #1
On Chrome, the result is that video #1 immediately starts playing (and buffering bar shows halfway loaded, which is the most I ever see at video start). Fiddler does NOT show a new "getfile" request to server.
On IE 11, there is a delay while video #1 buffers (and buffering bar shows zero loaded at video start). Fiddler DOES show a new "getfile" request to server.
IE's cache setting is "automatic". (Temporary Internet Files / Check for newer versions of stored pages = "Automatically"). Cache size is 250 mb, videos are ~ 6 mb each, and cache was emptied prior to start of testing.
Confirmed that URL is exactly the same (according to fiddler, and using alert pop-up in javascript).
Q: What else could affect IE's failure to cache these videos?
UPDATE
IMAGES, obtained via the same url, but with different query field fileid value, and different Content-Type header, ARE caching in IE: If quit browser, and restart browser, and go the the same page, Fiddler does not show any "/getfile.php?fileid=333" requests for those images. (It did show those requests the first time page was loaded after cache clear.)
The only change in php code executed (for images versus video) is a single if / else if statement, that controls what Content-Type header is emitted.
Perhaps it is IE 11's caching policy to not cache videos?
The logic does emit a Content-Length header with file size, and the client internet options cache (250 mbs) is much larger than the file size (6 mb), so it "should" be able to cache it. Disk space free is many GBs.
UPDATE #2
Restarting IE, after using Security tab to turn "Enable Protected Mode" off or on, does not change the above results.
Increasing disk space to the maximum (1024 MB) does not change the above results.
Setting IE's policy to "Check for newer versions of stored pages: Never" doesn't seem to "stick": when close Internet Options, then re-open it, the radio button has returned to "Automatically".
...
Repeating Chrome test after the IE tests confirms that caching is still working correctly within Chrome.
UPDATE #3
php code on server does NOT test for HTTP_IF_MODIFIED_SINCE; I'm not sending Last-Modified header. I was assuming maxage would be sufficient. It is possible that IE would be smarter about caching video files if Last-Modified was present. If you have any experience with video over slow server connections, and have succeeded using a specific set of headers, then an answer with the approach you used would be useful.
Give this a shot, from http://php.net/manual/en/function.header.php#85146:
$last_modified_time = filemtime($file);
$etag = md5_file($file);
header("Last-Modified: ".gmdate("D, d M Y H:i:s", $last_modified_time)." GMT");
header("Etag: $etag");
if (#strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified_time ||
trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag) {
header("HTTP/1.1 304 Not Modified");
exit;
}
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 have a php script acting as a random image generator. The script queries the database for the user's images, and returns the path to one, at random. Here is the portion of the code responsible for returning the image, once the path has been chosen.
header('Content-Transfer-Encoding: binary');
header('Content-Type: image/jpeg');
header('Content-Length: ' . filesize($path));
echo file_get_contents($path);
I am calling it from the client like so
image.src = "/database/getRandomImage.php";
Every time I refresh the page I get a new image at random. However, if I call getRandomImage.php multiple times for side by side images, they will all be the same image. If I add a random property to the call like so
image.src = "/database/getRandomImage.php?path=" + Math.random() * 100;
The pictures become random. I take this to mean that the browser is caching them based on the random property I passed. The problem is this property has nothing to do with the actual image. Two different images might get cached as the same image, and the same image might not be retrieved from the cache. Is there any way for getRandomImage.php to inform the browser about the picture it is sending back?
Why not have getRandomImage be a PHP function, which returns a path to the image. You can render the page out with the random image paths already filled in.
<img src="<? echo getRandomImage() ?>">
Then you can actually serve your images with real cache headers, and your bandwidth wont be getting hammered so hard.
Do this on the server side while the page is rendering, not after. Doing it after is more work and, as you are finding out, is more complicated too.
The caching has nothing to do with the PHP script; it happens at the browser.
Try adding this to the script, to try and force the browser to not cache it (from PHP website):
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
Just make randomImage.php redirect to a seeded version if it isn't present.
if (!isset($_REQUEST['seed']))
{
header("Location: randomImage.php?seed="+rand());
exit;
}
Feel free to make the randomizer more random.
Browsers expect that the same will always represent the same image. And I think that even headers that force no caching at all wont even stop the browser from reusing the image on the same page. So a image source that is different each time you call it is pretty counter intuitive.
Cache busting is probably your best bet. That means like you random hack there, although there is ways to do it better. For instance, appending an increasing integer to the current time. This way you never duplicate urls.
var count = 0;
var now = new Date().getTime();
imgs[0].src = "/database/getRandomImage.php?" + (count++) + now;
imgs[1].src = "/database/getRandomImage.php?" + (count++) + now;
imgs[2].src = "/database/getRandomImage.php?" + (count++) + now;
But really, you may want to rethink your strategy here, because it sounds a little fishy.
I've created a function that rotates defined image. It works perfect in firefox, but in IE and Opera nothing happens - the image is reloaded but not rotated. Does anybody know why? Here goes the code:
function rotateImage($direction, $id, $angle) {
$dir = opendir($direction);
if ($img = imagecreatefromjpeg($_SESSION['files'][$id]['large'])) {
$width = imagesx ( $img );
$height = imagesy ( $img );
$rotate = imagerotate($img, $angle, 0);
imagejpeg($rotate, $_SESSION['files'][$id]['large'], 100);
}
else {
echo '<p>Error: image cannot be rotated</p>';
}
closedir($dir);
}
The problem is definitely not with the browser you are using as the rotation is done server-side.
You might be running into a caching issue or an issue with the code used to call that function.
Are you:
Using JavaScript to initiate a reload?
Your JavaScript code might be the issue here.
Sending the proper no-cache headers?
If not, you might be running into a situation where the image is cached on the browser, which is why you are not seeing your changes. Either send the proper Cache-control and Expires headers, or append a random identifier to the image url (?_=$x where $x = time() will work fine... Headers are preferred).
Sending the proper Content-type header?
Not sending the proper headers might cause erratic behavior in some browsers. You might want to try using header('Content-type: image/jpeg')
Sending only the image data without any extra characters?
Make sure you don't output anything else than the image. Your output stream must not have any extra characters, including whitespaces.
Try hit refresh! Or clear cache and reload.
This is because the image is saved in browsers cache, and browser know it has it, but it doesn't know it has been changed. One of the tricks is to save the image on the server side with randomly generated name.
I would suspect you aren't sending an appropriate Content-Type header for the image. Alternatively, the image may be slightly corrupted (commonly caused by spaces before/after the php tags in your source code). Save the image from Firefox on your hard drive, open it in a text editor (such as Editplus) and check it doesn't start or end with a space.
PHP is server side, so if it works on one browser the code works fine and the issue lies with the browser. I would assume that IE and opera are caching the image. If possible set the headers for the images so that they don't get cached:
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past