i made an image resizer in php. When an image is resized, it caches a new jpg file with the new dimensions. Next time you call the exact img.php?file=hello.jpg&size=400 it checks if the new jpg has already been created.
If it has NOT been created yet, it creates the file and then prints the output (cool).
If it ALREADY exists, no new file needs to be generated and instead, it just calls the already cached file.
My question is regarding the second scenario. Which of these is faster?
redirecting: header('Location: cache/hello_400.jpg');die();
grabbing data and printing the cached file: $data = file_get_contents('cache/hello_400.jpg'); header('Content-type: '.$mime);
header('Content-Length: '.strlen($data));
echo $data;
Any other ways to improve this?
If someone wants the generated code, check this out:
http://egobits.com/misc/img.phps
Thanks to all for the help!
I would opt for never printing the data to the browser. Both scenarios should throw a permanent redirect to the generated image. Except if the image doesn't exist yet, it is created before the Location header is sent.
Edit:
Just to be clear about what I mean by permanent redirect...
header('HTTP/1.1 301 Moved Permanently');
header('Location: http://path/to/image');
Maybe you could do the following:
Set some directory for these images.
Link to images in this directory (<a href="/img/resizable/hello_400.jpg>).
Set your webserver to redirect to your php script if the image doesn't exist yet. If you are on Apache, a simple .htaccess will do. In PHP, you have $_SERVER["REQUEST_URI"] from which you can tell which image you should resize.
Your script saves and echoes the image (but is called only for the first time).
This way, your get some benefits:
The image is cached (in a proxy or a browser) as any other static file.
PHP doesn't have to be called for every request just to redirect or output statical data.
You leave the implementation of If-modified-since and other cache-related headers to the webserver.
And the links look nicer :-)
Example .htaccess in your /img/resizable folder:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* /img.php [L]
Any other ways to improve this?
Yes.
There is a way to send nothing but just an HTTP header: a conditional get.
You can take a look at the similar script, http://shiftingpixel.com/2008/03/03/smart-image-resizer/, for the implementation
As a third (more powerful) option: cache the image in a binary field in a database, and query the database for it.
Implement all three solutions and benchmark them.
I'm going to guess that the first option (redirect) will be the slowest in the real world, because it requires just as much effort as the second option (file_get_contents), but involves a second request and more overhead.
If possible in your case, you can also implement a function to directly set the url of the cached image in your html like:
<img src="<?php getImageUrl('hello.jpg', 400); ?>" />
getImageUrl() will return the url of the cached image if it exists else it will return the url to dynamically generate the resized image.
Related
I've noticed in Facebook's source code, that images are links to a PHP file, safe_image.php (or rsrc.php; it changes every now and then), with the name of the selected file appended to the end, such as:
https://external-lax3-2.xx.fbcdn.net/safe_image.php?imagename1234
Or sometimes they're the usual JPEG files with a random token appended to the end:
https://scontent-lax3-2.xx.fbcdn.net/v/t1.0-9/17353408_410522555967800_2778489440067836960_n.jpg?oh=3e00f84c6767364c9304b34f8751114d&oe=5954DA1E
What, I'm wondering is how they get a custom image viewer on their website. Usually, it's just a white background, with the selected image in the top left corner. However, they have it set in the middle with a grey-ish background.
Not only that, the linked image is direct back to the viewed PHP file; how is this possible, and how do they do it?
Cheers.
EDIT: I've also noticed if you change the img src to an invalid link, it will print an error to the page:
The image " Insert image link here " cannot be displayed because it contains errors.
Jpeg files, to no ones surprise, do not take in arguments. However, PHP does. So, what is most likely Facebook did is use a rewrite rule to 'map' their .jpg?= URL to a PHP file, which can process the arguments. That PHP file then fetches the image data from a MYSQL (like) table. If you're wondering, yes you can have the .jpg file extension display in the URL, load data from PHP, and have the image display properly in browser.
This can be achieved via PHP and .htaccess.
Firstly, let's setup our .htaccess inside whatever folder we want to have our /img.jpg?= inside of:
Options +FollowSymLinks
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^img.jpg(.*)$ imageBackground.php$1 [NC]
Yay, now we are mapping our img.jpg?image=bill to imageBackground.php?image=bill.
And then we are going to write our imageBackground.php. I wrote a very simple one, but basically all your doing is setting up headers (for which image format you're using) as well as display the image data. Obviously in practical application this would be more complicated, like maybe you're dynamically grabbing image data from a database (like Facebook).
<?php
if ($_GET['image'] == "bill") {
header("Content-type: image/pjpeg");
echo file_get_contents("bill.jpg");
}
?>
I am building a small web app that will allow a logged in user to upload product pictures. I'm trying to build-it-right, and after some naive implementations in the past, decided to add all uploaded content in my public folder.
\app (public folder)
-\index.php
\includes
\config
\uploads
This would make it so if a malicious user managed to upload a PHP file it wouldn't be reachable or served by server.
That being said, how would I go about serving these pictures on the webpage? What is the best way to do this? In the past I've used a getImage.php file that would take an ID parameter (and a hash of some generated value to avoid users just guessing IDs) which would look like "getImage.php?id=555&c=44j54k3h5". The server grabs the image information in the DB (the real path) and loads the content, rebuilds the header, and sends the image.
Are there any security concerns regarding this method? Is there a way to make it prettier? Would having a htaccess rewrite rule that looks for /images/imagename.jpg and sends them to my getImage.php file add security holes? Any advice on best practices would be greatly appreciated.
Check out this class. It will handle the upload, do security checks and make you coffee :)
http://www.verot.net/php_class_upload.htm
The security of your method of serving the uploaded image, really do depend on a lot of other factors.
How is the image/file treated and secured on upload? What are the
directory/file permissions for the upload?
What processes have accessto read from and write to those locations?
What type of file handlers do you have that may execute arbitrary files?
If you're going the PHP->get_image->display_image route, does it attempt to prevent malicious intent AFTER upload (on the getimage?)
I would use Apache mod_alias to reference the directory above webroot. This way, you would be able to still utilize links to images without opening the full above-root directory to exposure.
The "prettier" method would be to use a .htaccess redirect
RewriteEngine On
RewriteBase /
RewriteRule ^getImage/([0-9]+)/(.+)\.png$ getImage.php?id=$1&c=$2 [NC,L]
Then in getImage.php
if (isset($_GET['id']) && isset($_GET['c'])) {
// set headers
header("Content-Type: image/png");
// cache control
header("Cache-Control: private, max-age=10800, pre-check=10800");
header("Pragma: private");
header("Expires: " . date(DATE_RFC822, strtotime("2 day")));
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
header('Last-Modified: ' . $_SERVER['HTTP_IF_MODIFIED_SINCE'], true, 304);
exit;
}
/* do things */
imagepng($image_resource);
imagedestroy($image_resource);
}
else {
echo "bad request";
}
So instead of
site.com/getImage.php?id=555&c=44j54k3h5
You would use
site.com/getImage/555/44j54k3h5.png
There aren't any added security risks or loop hole with this method. Just make sure your get variables are escaped before querying to avoid injection attacks.
If you want to protect yourself from malicious file uploads, you need to do that before serving.
I'm working on setting up a simple pixel tracking script with PHP, and the below technically works, but when I look at the inspector in Safari I get the following warning (1by1.gif is a 42B gif):
esource interpreted as document but
transferred with MIME type image/gif.
header("Content-type: image/gif");
header("Content-Length: 42");
echo file_get_contents("/path/to/1by1.gif");
// do tracking stuff below here
I've looked at other tracking pixels, and they all show in the inspector as if they are an actual image, even with the .php extension. Any ideas how to fix that warning?
EDIT:
I tried doing the following and I get the same warning:
header("Content-type: image/gif");
$img = imagecreatefromstring(file_get_contents("/path/to/1by1.gif"));
imagegif($img);
You could write 1x1.gif (or some other made up name) in your HTML source and then have Apache actually serve the PHP script. You can do this with .htaccess with something along the lines of:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^1x1\.gif$ tracking_script.php [NC,L]
This way Safari sees the gif extension and won't complain.
Well this is interesting. If I remove the content-length and just use the following, it appears to work perfectly. Anyone know why that might be?
header("Content-type: image/gif");
echo file_get_contents("/path/to/1by1.gif");
// do tracking stuff below here
I'm a puzzled. Function file_get_contents() is for getting content from a text file. What is your intend here? The function returns the content as string or false. Your echo statement essentially transfers that result which is correctly interpreted as document and not a gif.
Update: Took me a while to even reproduce this and see the warning. Echoing the file_get_contents() shows indeed the gif in the browser, so does a simple include() which also shows the warning. Does this warning causes you any trouble or is this just for a beauty contest? I can only speculate that the Safari's Inspector is a little picky. The same tool in Chrome does not show a warning.
$img = imagecreatefromstring(file_get_contents("/path/to/1by1.gif"));
imagegif($img);
This code
Reads in a GIF image
Passes the image bytes to GD
Asks GD to write a new image.
Many things could go wrong here. For example, writing the image back out might not produce the same exact stream of bytes, maybe more, maybe less. This could make your Content-Length header invalid, and browsers don't like it when you lie about such stuff. Or maybe there's a Notice or Warning in one of the lines, which would be emitted as content before the GIF data. That would certainly look like a "document" instead of as image data to Webkit.
Serving the file through file_get_contents / include / echo eliminates the filter-through-GD step. If the code works properly without that step, the error was somewhere there.
I am trying to display an image from a MySQL blob field. I have tried a few different things and none of them seem to work.
I have tried:
header("Content-type: $type"); img src = $blobData;
header("Content-type: $type"); echo($blobData);
<?php
header("Content-type: $type");
echo $blobData;
?>
This code looks perfectly OK. However, I heard a similar complain from another person and I was able to troubleshoot it by assuring that:
The php script does not output any extra character before or after sending the binary image data.
The php script is saved as a pure ASCII text file, not as a Unicode/UTF-8 encoded file. The Unicode/UTF-8 encoded PHP files might include a signature as the first bytes. These bytes will be invisible in your text editor but server will send these few extra bytes to the browser before the JPEG/GIF/PNG data. The browser will therefore find the wrong signature in the beginning of data. To workaround, create a blank text file in notepad, paste in the php code and save the file in ANSI encoding.
Another option you might consider (assuming you are on Apache):
Create an .htaccess file with a mod_rewrite for all image extensions (png, jpg, gif).
Have it redirect to a php script that looks up the image requested in the DB. If it is there, it echos out the header and BLOG. If it isn't there, it returns a standard 404.
This way you can have:
<img src="adorablepuppy.jpg" />
Which then gets redirected ala:
RewriteEngine on
RewriteRule \.(gif|jpg|png)$ imagelookup.php
This script does a query for the image, which (obviously) assumes that the requested image has a unique key that matches the filename in the URL:
$url = $_SERVER['REQUEST_URI'];
$url_parts = explode("/", $url);
$image_name = array_pop($url_parts);
Now you have just the image filename. Do the query (which I shall leave up to you, along with any validation methods and checks for real files at the address, etc.).
If it comes up with results:
header('Content-type: image/jpeg');
header('Content-Disposition: inline; filename="adorablepuppy.jpg"');
print($image_blog);
otherwise:
header("HTTP/1.0 404 Not Found");
FYI: I have no idea if this would be bad in terms of performance. But it would allow you to do what I think you want, which is output the image as though it were a flat image file on the server using a simple image element. I'm inclined to agree that BLOBs are not the best way to go, but this does avoid any cross-browser issues.
I believe that the issue that you are encountering is an issue with encoding. This resource claims that you can use the print function.
Just get the image from the database. And print it using the correct headers.
$image = mysql_fetch_array(...)
header("Content-type: image/jpeg"); // change it to the right extension
print $image['data'];
For performance reasons... this is not advisable. There are several reasons to put images in databases but the most common are:
a) keeping them indexed (duh!)
You can do this by storing the images flat on the server and just indexing the image filename.
b) keeping the image hidden/protected
Flickr and alike still store the images flat on the server and use a different approach. They generate a URL thats hard to find.
This link points to a protected image on my account. You can still access it once you know the correct URL. Try it!
farm2.static - a farm optimized for delivering static content
1399 - perhaps the server
862145282 - my username
bf83f25865_b - the image
In order to find all my secret images any user can hard hit Flickr with the above address and change the last part. But it would take ages and the user would probably be blocked for hammering the server with thousands of 404s.
That said there is little reason to store images on BLOBs.
Edit:Just a link pointing to someone that explained much better than I did why BLOB is not the way to go when storing images.
What would be the best practice way to handle the caching of images using PHP.
The filename is currently stored in a MySQL database which is renamed to a GUID on upload, along with the original filename and alt tag.
When the image is put into the HTML pages it is done so using a url such as '/images/get/200x200/{guid}.jpg which is rewritten to a php script. This allows my designers to specify (roughly - the source image maybe smaller) the file size.
The php script then creates a hash of the size (200x200 in the url) and the GUID filename and if the file has been generated before (file with the name of the hash exists in TMP directory) sends the file from the application TMP directory. If the hashed filename does not exist, then it is created, written to disk and served up in the same manner,
Is this efficient as it could be? (It also supports watermarking the images and the watermarking settings are stored in the hash as well, but thats out of scope for this.)
I would do it in a different manner.
Problems:
1. Having PHP serve the files out is less efficient than it could be.
2. PHP has to check the existence of files every time an image is requested
3. Apache is far better at this than PHP will ever be.
There are a few solutions here.
You can use mod_rewrite on Apache. It's possible to use mod_rewrite to test to see if a file exists, and if so, serve that file instead. This bypasses PHP entirely, and makes things far faster. The real way to do this, though, would be to generate a specific URL schema that should always exist, and then redirect to PHP if not.
For example:
RewriteCond %{REQUEST_URI} ^/images/cached/
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f
RewriteRule (.*) /images/generate.php?$1 [L]
So if a client requests /images/cached/<something> and that file doesn't exist already, Apache will redirect the request to /images/generate.php?/images/cached/<something>. This script can then generate the image, write it to the cache, and then send it to the client. In the future, the PHP script is never called except for new images.
Use caching. As another poster said, use things like mod_expires, Last-Modified headers, etc. to respond to conditional GET requests. If the client doesn't have to re-request images, page loads will speed dramatically, and load on the server will decrease.
For cases where you do have to send an image from PHP, you can use mod_xsendfile to do it with less overhead. See the excellent blog post from Arnold Daniels on the issue, but note that his example is for downloads. To serve images inline, take out the Content-Disposition header (the third header() call).
Hope this helps - more after my migraine clears up.
There is two typos in Dan Udey's rewrite example (and I can't comment on it), it should rather be :
RewriteCond %{REQUEST_URI} ^/images/cached/
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f
RewriteRule (.*) /images/generate.php?$1 [L]
Regards.
One note worth adding is to make sure you're code does not generate "unauthorized" sizes of these images.
So the following URL will create a 200x200 version of image 1234 if one doesn't already exist. I'd highly suggest you make sure that the requested URL contains image dimensions you support.
/images/get/200x200/1234.jpg
A malicious person could start requesting random URLs, always altering the height & width of the image. This would cause your server some serious issues b/c it will be sitting there, essentially under attack, generating images of sizes you do not support.
/images/get/0x1/1234.jpg
/images/get/0x2/1234.jpg
...
/images/get/0x9999999/1234.jpg
/images/get/1x1/1234.jpg
...
etc
Here's a random snip of code illustrating this:
<?php
$pathOnDisk = getImageDiskPath($_SERVER['REQUEST_URI']);
if(file_exists($pathOnDisk)) {
// send header with image mime type
echo file_get_contents($pathOnDisk);
exit;
} else {
$matches = array();
$ok = preg_match(
'/\/images\/get\/(\d+)x(\d+)\/(\w+)\.jpg/',
$_SERVER['REQUEST_URI'], $matches);
if(! $ok) {
// invalid url
handleInvalidRequest();
} else {
list(, $width, $height, $guid) = $matches;
// you should do this!
if(isSupportedSize($width, $height)) {
// size is supported. all good
// generate the resized image, save it & output it
} else {
// invalid size requested!!!
handleInvalidRequest();
}
}
}
// snip
function handleInvalidRequest() {
// do something w/ invalid request
// show a default graphic, log it etc
}
?>
Seems great post, but my problem still remains unsolved. I dont have access to htaccess in my host provider, so no question of apache tweaking. Is there really a way to set cace-control header for images?
Your approach seems quite reasonable - I would add that some mechanism should be put into place to check that the date the cached version was generated was after the last modified timestamp of the original (source) image file and if not regenerate the cached/resized version. This will ensure that if an image is changed by the designers the cache will be updated appropriately.
That sounds like a solid way to do it. The next step may be to go beyond PHP/MySQL.
Perhaps, tweak your headers:
If you're using PHP to send MIME types, you might also use 'Keep-alive' and 'Cache-control' headers to extend the life of your images on the server and take some of the load off of PHP/MySQL.
Also, consider apache plugin(s) for caching as well. Like mod_expires.
Oh, one more thing, how much control do you have over your server? Should we limit this conversation to just PHP/MySQL?
I've managed to do this simply using a redirect header in PHP:
if (!file_exists($filename)) {
// *** Insert code that generates image ***
// Content type
header('Content-type: image/jpeg');
// Output
readfile($filename);
} else {
// Redirect
$host = $_SERVER['HTTP_HOST'];
$uri = rtrim(dirname($_SERVER['PHP_SELF']), '/\\');
$extra = $filename;
header("Location: http://$host$uri/$extra");
}
Instead of keeping the file address in the db I prefer adding a random number to the file name whenever the user logs in. Something like this for user 1234: image/picture_1234.png?rnd=6534122341
If the user submits a new picture during the session I just refresh the random number.
GUID tackles the cache problem 100%. However it sort of makes it harder to keep track of the picture files. With this method there is a chance the user might see the same picture again at a future login. However the odds are low if you generate your random number from a billion numbers.
phpThumb is a framework that generates resized images/thumbnails on the fly. It also implements caching and it's very easy to implement.
The code to resize an image is:
<img src="/phpThumb.php?src=/path/to/image.jpg&w=200&h=200" alt="thumbnail"/>
will give you a thumbnail of 200 x 200;
It also supports watermarking.
Check it out at:
http://phpthumb.sourceforge.net/