How are images cached when loaded via php script - php

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.

Related

PHP URL string to avoid browser caching

My site is designed to be a funny picture site, when the user hits the random button a PHP code on the same page generates a new random picture, this is how it is supposed to work. I however have to hit the F5 button to get a new image.
I was reading on another question that people use a get date and get time query string generated at the end of the link to avoid browser caching, I however can not figure it out for the life of me.
I am not very good with php so please speak as if I only know the basic webpage structure. Thank you!
What you are describing is called a cache breaker and is usually a random string or a timestamp appended to the url. When you are referencing your image, prepend it like this:
echo get_random_image_url() . '?' . time();
This will result in an url looking like this:
http://your.server.com/random.jpg?1355862360
Note: get_random_image_url is just an example, but i'm sure you get the idea.
This thread may be of interest: How to force a web browser NOT to cache images.
i think using headers is better than the url trick
<?php
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
?>
http://php.net/manual/en/function.header.php
It is very easy to be solved: for example,
Check the following two links:
http://cdn.arstechnica.net/wp-content/uploads/2012/10/06_Place_20773_1_Mis.jpg
http://cdn.arstechnica.net/wp-content/uploads/2012/10/06_Place_20773_1_Mis.jpg?randomValue
Both of the two links will be open the same image.
This is your solution! You have to add at the end of your image file name a random value:
image.png?<?php echo someRandom();?>
This community or Google for a way to write a function that gemnerates random values.
Also there is solution using javascript, suupose the following
<img id="funny" src="scripts/php_rand_image.php" />
Get another image
<script>
function changeImage(ob){
image = document.getElementById(ob)
d = new Date();
image.src = image.src+'?'+d.getTime();
}
</script>

Developing a tracking pixel

I am trying to build a pixel that would track the current URL the user is on when they visit. I can use either JS (preferred) or a 1x1 image pixel. With JS I am assuming that I'd need to run an AJAX request to a PHP script to capture the info that I need and with an image pixel I am having issues getting the currently URL.
I also thought about URL encoding the current URL with JS and dynamically placing the image pixel with the encoded current URL as a query string to a PHP script, but that I can get to be very long.
If I am to go the AJAX route, which AJAX library can I use? JQuery is too bloated for this purpose.
Any other ideas?
You can write a script that creates and returns a .gif, .jpeg or .png image using PHP for tracking purposes using the GD library (which is often distributed with PHP in modern versions). If you don't have access to GD, you can always recompile PHP with GD enabled.
Example:
pixel.php (commented for the purposes of explanation):
<?php
// Create an image, 1x1 pixel in size
$im=imagecreate(1,1);
// Set the background colour
$white=imagecolorallocate($im,255,255,255);
// Allocate the background colour
imagesetpixel($im,1,1,$white);
// Set the image type
header("content-type:image/jpg");
// Create a JPEG file from the image
imagejpeg($im);
// Free memory associated with the image
imagedestroy($im);
?>
In a simple example, you can then call this tracking pixel using the following example URL in an email or other page:
<img src="http://example.com/pixel.php?a=value1&b=value2&c=value3">
Using variables:
Within your pixel.php you can then parse and interpret any $_GET variables that are passed to it within the image tag, simplistically:
if (isset($_GET['a'])) {
// (Do|log) act on a
}
if (isset($_GET['b'])) {
// (Do|log) act on b
}
if (isset($_GET['c'])) {
// (Do|log) act on c
}
Apply and repeat as you need, but you can be quite sophisticated about what you do and especially as you have access to quite a lot of information about the user through being able to set vars on the $_GET string.
A more applicable example might be:
<img src="http://example.com/pixel.php?userid=98798&campaign=302&last=8">
Tracking more than just $_GET variables:
You can also pick up much more information using PHP, such as:
// Server variables
$ip = $_SERVER['REMOTE_ADDR'];
$referer = $_SERVER['HTTP_REFERER'];
$useragent = $_SERVER['HTTP_USER_AGENT'];
$browser = get_browser(null, true);
etc...
and then perhaps insert into a tracking table in your database:
$sql = "INSERT INTO campaign_tracking
('when','campaign','last','ip','useragent')
VALUES
(NOW(),'$campaign','$last','$ip','$useragent')";
This is a(the) basic method used widely for tracking email marketing campaigns and specifically in PHP, but the same method is applicable using other scripting/programming languages and libraries - and for other purposes too.
Further and useful information on GD:
GD reference - on php.net
Here is another PHP implementation of a tracking pixel, from the Open Web Analytics project, which attempts to basically be a PHP clone of Google Analytics.
It returns a 1x1 transparent GIF image (without using a PHP image library!), with a no-cache header (important for accurate tracking), and flushes the output so you can continue processing the analytics without blocking the HTTP response (performance). It seems like a pretty advanced implementation, worth trying out.
<?php
ignore_user_abort(true);
// turn off gzip compression
if ( function_exists( 'apache_setenv' ) ) {
apache_setenv( 'no-gzip', 1 );
}
ini_set('zlib.output_compression', 0);
// turn on output buffering if necessary
if (ob_get_level() == 0) {
ob_start();
}
// removing any content encoding like gzip etc.
header('Content-encoding: none', true);
//check to ses if request is a POST
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// the GIF should not be POSTed to, so do nothing...
echo ' ';
} else {
// return 1x1 pixel transparent gif
header("Content-type: image/gif");
// needed to avoid cache time on browser side
header("Content-Length: 42");
header("Cache-Control: private, no-cache, no-cache=Set-Cookie, proxy-revalidate");
header("Expires: Wed, 11 Jan 2000 12:59:00 GMT");
header("Last-Modified: Wed, 11 Jan 2006 12:59:00 GMT");
header("Pragma: no-cache");
echo sprintf('%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%',71,73,70,56,57,97,1,0,1,0,128,255,0,192,192,192,0,0,0,33,249,4,1,0,0,0,0,44,0,0,0,0,1,0,1,0,0,2,2,68,1,0,59);
}
// flush all output buffers. No reason to make the user wait for OWA.
ob_flush();
flush();
ob_end_flush();
// DO ANALYTICS TRACKING HERE
Output 1px x 1px this way:
header('Content-type: image/png');
echo gzinflate(base64_decode('6wzwc+flkuJiYGDg9fRwCQLSjCDMwQQkJ5QH3wNSbCVBfsEMYJC3jH0ikOLxdHEMqZiTnJCQAOSxMDB+E7cIBcl7uvq5rHNKaAIA'));
Here's an extremely simplified tracking pixel written in PHP.
How a Tracking Pixel Works
A tracking pixel is like the most primitive beacon possible, and it operates by exploiting a fact of web pages: images are a separate request from the page.
If you are already able to run your JS code on someone else's page, you should just POST the data back to your server. No need to display a tiny pixel that will only get the same kind of data.
It is a similar problem with this effect, since a call to a function to execute a mark of when the email was seen or opened was introduced in the alt of the pixel, but it does not throw the action correctly.
<img src="https://datafeeds.baruwa.com/1x1spacer.gif" width="1" height="1" alt="Web Bug from https://devorpenguin.des1.net/module/cartabandonmentpro/FrontCartAbandonment?token_cart=87c83b8f77318a54fdd6be91aacc3574&id_cart=1002&action=visualize&wichRemind=1">
public static function visualize()
{
$wichRemind = Tools::getValue('wichRemind');
$id_cart = Tools::getValue('id_cart');
$token = Tools::getValue('token_cart');
if ($token == md5(_COOKIE_KEY_.'recover_cart_'.$id_cart)) {
$query = "UPDATE "._DB_PREFIX_."cartabandonment_remind SET visualize = 1 WHERE wich_remind = ".(int)$wichRemind." AND id_cart = ".(int)$id_cart;
Db::getInstance()->Execute($query);
}
header('Content-Type: image/png');
echo base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=');
}
Using OpenPixel will take care of most of the heavy lifting if the scope of your project calls for it.

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

GD - rotating image doesn't work in IE and Opera

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

Best way to initiate a download?

On a PHP-based web site, I want to send users a download package after they have filled out a short form. The site-initiated download should be similar to sites like download.com, which say "your download will begin in a moment."
A couple of possible approaches I know about, and browser compatibility (based on a quick test):
1) Do a window.open pointing to the new file.
- FireFox 3 blocks this.
- IE6 blocks this.
- IE7 blocks this.
2) Create an iframe pointing to the new file.
- FireFox 3 seems to think this is OK. (Maybe it's because I already accepted it once?)
- IE6 blocks this.
- IE7 blocks this.
How can I do this so that at least these three browsers will not object?
Bonus: is there a method that doesn't require browser-conditional statements?
(I believe that download.com employs both methods conditionally, but I can't get either one to work.)
Responses and Clarifications:
Q: "Why not point the current window to the file?"
A: That might work, but in this particular case, I want to show them some other content while their download starts - for example, "would you like to donate to this project?"
UPDATE: I have abandoned this approach. See my answer below for reasons.
You can also do a meta refresh, which most browsers support. Download.com places one in a noscript tag.
<meta http-equiv="refresh" content="5;url=/download.php?doc=123.zip"/>
Update: I have decided to abandon this approach, and instead just present the user with a link to the actual file. My reasoning is this:
My initial attempts at a server-initiated download were blocked by the browser. That got me thinking: "the browser is right. How does it know that this is a legitimate download? It should block a download that isn't obviously user-initiated."
Any method that I can use for a server-initiated download could also be used by someone who wants to send malware. Therefore, downloads should only happen when the user specifically requests the file by clicking on a link for it.
You're free to disagree, and if you still want to initiate a download, hopefully this thread will help you do it.
I usually just have a PHP script that outputs the file directly to the browser with the appropriate Content-Type
if(file_exists($filename)) {
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, pre-check=0");
header("Cache-Control: private", false);
header("Content-Type: " . $content-type);
header("Content-Disposition: attachment; filename=\"" . basename($filename) . "\";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . filesize($filename));
readfile("$filename");
}else{
print "ERROR: the file " . basename($filename) . " could not be downloaded because it did not exist.";
}
The only disadvantage is that, since this sets the HTTP header, it has be called before you have any other output.
But you can have a link to the PHP download page and it will cause the browser to pop up a download box without messing up the content of the current page.
One catch is that you may encounter issues with IE (version 6 in particular) if the headers are not set up "correctly".
Ensure you set the right Content-Type, but also consider setting the Cache options for IE (at least) to allow caching. If the file is one the user can open rather than save (e.g. an MS Word document) early versions of IE need to cache the file, as they hand off the "open" request to the applicable app, pointing to the file that was downloaded in the cache.
There's also a related issue, if the IE6 user's cache is full, it won't properly save the file (thus when the applicable app gets the hand off to open it, it will complain the file is corrupt.
You may also want to turn of any gzip actions on the downloads too (for IE)
IE6/IE7 both have issues with large downloads (e.g. 4.x Gigs...) not a likely scenario since IE doesn't even have a download manager, but something to be aware of.
Finally, IE6 sometimes doesn't nicely handle a download "push" if it was initiated from within a nested iframe. I'm not exactly sure what triggers the issue, but I find it is easier with IE6 to avoid this scenario.
Hoi!
#Nathan:
I decided to do exactly that: Have my "getfile.php" load all necessary stuff and then do a
header("Location: ./$path/$filename");
to let the browser itself and directly do whatever it thinks is correct do with the file. This even works fine in Opera with me.
But this will be a problem in environments, where no direct access to the files is allowed, in that case you will have to find a different way! (Thank Discordia my files are public PDFs!)
Best regards, Basty
How about changing the location to point to the new file? (e.g. by changing window.location)
I've always just made an iframe which points to the file.
<iframe src="/download.exe" frameborder="0" height="0" width="0">Click here to download.</iframe>
Regarding not pointing the current window to the download.
In my experience you can still show your "please donate" page, since downloads (as long as they send the correct headers) don't actually update the browser window.
I do this for csv exports on one of my sites, and as far as the user is concerned it just pops up a safe file window.
So i would recommend a simple meta-redirect as Soldarnal showed.
Just to summarise, you have 2 goals:
start download process
show user a page with a donate options
To achieve this I would do the following:
When your user submits the form, he gets the resulting page with a donate options and a text saying that his download will start in 5 seconds. And in the head section of this page you put the META code as Soldarnal said:
<meta http-equiv="refresh" content="5;url=/download.php?doc=123.zip>
And that's all.
<a href="normaldownload.zip" onclick="use_dhtml_or_ajax_to_display_page()">
Current page is unaffected if download is saved. Just ensure that download doesn't open in the same window (proper MIME type or Content-Disposition) and you'll be able to show anything.
See more complete answer
You can use Javascript/jQuery to initiate the download. Here's an example - you can get rid of the Ajax request and just use the setTimeout() block.
$("btnDownloadCSV").on('click', function() {
$.ajax({
url: "php_backend/get_download_url",
type: 'post',
contentType: "application/x-www-form-urlencoded",
data: {somedata: "somedata"},
success: function(data) {
// If iFrame already exists, remove it.
if($("[id^='iframeTempCSV_"]).length) {
$("[id^='iframeTempCSV_"]).remove();
}
setTimeout(function() {
// If I'm creating an iframe with the same id, it will permit download only the first time.
// So randHashId appended to ID to trick the browser.
var randHashId = Math.random().toString(36).substr(2);
// Create a fresh iFrame for auto-downloading CSV
$('<iframe id="iframeTempCSV_'+randHashId+'" style="display:none;" src="'+data.filepath+'"></iframe>').appendTo('body');
}, 1000);
},
error: function(xhr, textStatus, errorThrown) {
console.error("Error downloading...");
}
});
});

Categories