Caching image thumbnail generator in thumbnails: how to set Ignored / Max-Age / Last-Modified HEADERS in PHP?

Even after a very high score from Google PageSpeed ​​( 97 ) and Yahoo! The YSlow ( 92 ) generated PHP sketches do not seem to passively come from the old cache: they seem to be generated every time again ... and again ... freshly baked time consuming a lot of waist.

This question will focus only and specifically on how to solve the PHP CACHE code problem that generates thumbs:

Just take a look at these tiny little small miniatures measuring only 3 ~ 5 kb each!

Waterfall detail: http://www.webpagetest.org/result/110328_AM_8T00/1/details/

Any suggestions are +1 help and I warmly welcome you, because I have really despaired of this issue in recent months. Thanx a Thousand!

The use or absence of Modrewrite does not affect speed: both are the same. I use these rewrite conditions: RewriteCond %{REQUEST_URI} ^/IMG-.*$ And RewriteCond %{REQUEST_FILENAME} !-f

Both the default source URL and also decorated with rewritten URL give the same delay! So let's not point out the error at lightning fast Apache: its PHP cache / headers, which are somehow erroneously encoded ...

enter image description here


Warning from webpagetest.org: Using static asset caching in a browser: 69/100

FAILED - (no maximum age or expires): http://aster.nu/imgcpu?src=aster_bg/124.jpg&w=1400&h=100&c=p


After each update, you will see either of these two warnings appear randomly on REDbot.org enter image description hereenter image description here


Relevant parts of the code:

 // Script is directly called if(isset($_GET['src']) && (isset($_GET['w']) || isset($_GET['h']) || isset($_GET['m']) || isset($_GET['f']) || isset($_GET['q']))){ $ImageProcessor = new ImageProcessor(true); $ImageProcessor->Load($_GET['src'], true); $ImageProcessor->EnableCache("/var/www/vhosts/blabla.org/httpdocs/tmp/", 345600); $ImageProcessor->Parse($quality); } /* Images processing class * - create image thumbnails on the fly * - Can be used with direct url imgcpu.php?src= * - Cache images for efficiency */ class ImageProcessor { private $_image_path; # Origninal image path protected $_image_name; # Image name string private $_image_type; # Image type int protected $_mime; # Image mime type string private $_direct_call = false; # Is it a direct url call? boolean private $_image_resource; # Image resource var Resource private $_cache_folder; # Cache folder strig private $_cache_ttl; # Cache time to live int private $_cache = false; # Cache on boolean private $_cache_skip = false; # Cache skip var boolean private function cleanUrl($image){ # Cleanup url $cimage = str_replace("\\", "/", $image); return $cimage; } /** Get image resource * @access private, @param string $image, @param string $extension, @return resource */ private function GetImageResource($image, $extension){ switch($extension){ case "jpg": @ini_set('gd.jpeg_ignore_warning', 1); $resource = imagecreatefromjpeg($image); break; } return $resource; } /* Save image to cache folder * @access private, @return void */ private function cacheImage($name, $content){ # Write content file $path = $this->_cache_folder . $name; $fh = fopen($path, 'w') or die("can't open file"); fwrite($fh, $content); fclose($fh); # Delete expired images foreach (glob($this->_cache_folder . "*") as $filename) { if(filemtime($filename) < (time() - $this->_cache_ttl)){ unlink( $filename ); } } } /* Get an image from cache * @access public, @param string $name, @return void */ private function cachedImage($name){ $file = $this->_cache_folder . $name; $fh = fopen($file, 'r'); $content = fread($fh, filesize($file)); fclose($fh); return $content; } /* Get name of the cache file * @access private, @return string */ private function generateCacheName(){ $get = implode("-", $_GET); return md5($this->_resize_mode . $this->_image_path . $this->_old_width . $this->_old_height . $this->_new_width . $this->_new_height . $get) . "." . $this->_extension; } /* Check if a cache file is expired * @access private, @return bool */ private function cacheExpired(){ $path = $this->_cache_folder . $this->generateCacheName(); if(file_exists($path)){ $filetime = filemtime($path); return $filetime < (time() - $this->_cache_ttl); }else{ return true; } } /* Lazy load the image resource needed for the caching to work * @return void */ private function lazyLoad(){ if(empty($this->_image_resource)){ if($this->_cache && !$this->cacheExpired()){ $this->_cache_skip = true; return; } $resource = $this->GetImageResource($this->_image_path, $this->_extension); $this->_image_resource = $resource; } } /* Constructor * @access public, @param bool $direct_call, @return void */ public function __construct($direct_call=false){ # Check if GD extension is loaded if (!extension_loaded('gd') && !extension_loaded('gd2')) { $this->showError("GD is not loaded"); } $this->_direct_call = $direct_call; } /* Resize * @param int $width, @param int $height, @param define $mode * @param bool $auto_orientation houd rekening met orientatie wanneer er een resize gebeurt */ public function Resize($width=100, $height=100, $mode=RESIZE_STRETCH, $auto_orientation=false){ // Validate resize mode $valid_modes = array("f", "p"); } // .... omitted ..... // Set news size vars because these are used for the // cache name generation // .... omitted ..... $this->_old_width = $width; $this->_old_height = $height; // Lazy load for the directurl cache to work $this->lazyLoad(); if($this->_cache_skip) return true; // Create canvas for the new image $new_image = imagecreatetruecolor($width, $height); imagecopyresampled($new_image, $this->_image_resource, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h); // .... omitted ..... $this->_image_resource = $new_image; } /* Create image resource from path or url * @access public, @param string $location, @param bool $lazy_load, @return */ public function Load($image,$lazy_load=false){ // Cleanup image url $image = $this->cleanUrl($image); // Check if it is a valid image if(isset($mimes[$extension]) && ((!strstr($image, "http://") && file_exists($image)) || strstr($image, "http://")) ){ // Urlencode if http if(strstr($image, "http://")){ $image = str_replace(array('http%3A%2F%2F', '%2F'), array('http://', '/'), urlencode($image)); } $image = str_replace("+", "%20", $image); $this->_extension = $extension; $this->_mime = $mimes[$extension]; $this->_image_path = $image; $parts = explode("/", $image); $this->_image_name = str_replace("." . $this->_extension, "", end($parts)); // Get image size list($width, $height, $type) = getimagesize($image); $this->_old_width = $width; $this->_old_height = $height; $this->_image_type = $type; }else{ $this->showError("Wrong image type or file does not exists."); } if(!$lazy_load){ $resource = $this->GetImageResource($image, $extension); $this->_image_resource = $resource; } } /* Save image to computer * @access public, @param string $destination, @return void */ public function Save($destination, $quality=60){ if($this->_extension == "png" || $this->_extension == "gif"){ imagesavealpha($this->_image_resource, true); } switch ($this->_extension) { case "jpg": imagejpeg($this->_image_resource,$destination, $quality); break; case "gif": imagegif($this->_image_resource,$destination); break; default: $this->showError('Failed to save image!'); break; } } /* Print image to screen * @access public, @return void */ public function Parse($quality=60){ $name = $this->generateCacheName(); $content = ""; if(!$this->_cache || ($this->_cache && $this->cacheExpired())){ ob_start(); header ("Content-type: " . $this->_mime); if($this->_extension == "png" || $this->_extension == "gif"){ imagesavealpha($this->_image_resource, true); } switch ($this->_extension) { case "jpg": imagejpeg($this->_image_resource, "", $quality); break; case "gif": imagegif($this->_image_resource); break; default: $this->showError('Failed to save image!'); break; } $content = ob_get_contents(); ob_end_clean(); }else{ if (isset ($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { if (strtotime ($_SERVER['HTTP_IF_MODIFIED_SINCE']) < strtotime('now')) { header ('HTTP/1.1 304 Not Modified'); die (); } } // change the modified headers $gmdate_expires = gmdate ('D, d MYH:i:s', strtotime ('now +10 days')) . ' GMT'; $gmdate_modified = gmdate ('D, d MYH:i:s') . ' GMT'; header ("Content-type: " . $this->_mime); header ('Accept-Ranges: bytes'); header ('Last-Modified: ' . $gmdate_modified); header ('Cache-Control: max-age=864000, must-revalidate'); header ('Expires: ' . $gmdate_expires); echo $this->cachedImage($name); exit(); } // Save image content if(!empty($content) && $this->_cache){ $this->cacheImage($name, $content); } // Destroy image $this->Destroy(); echo $content; exit(); } /* Destroy resources * @access public, @return void */ public function Destroy(){ imagedestroy($this->_image_resource); } /* Get image resources * @access public, @return resource */ public function GetResource(){ return $this->_image_resource; } /* Set image resources * @access public, @param resource $image, @return resource */ public function SetResource($image){ $this->_image_resource = $image; } /* Enable caching * @access public, @param string $folder, @param int $ttl, * @return void */ public function EnableCache($folder="/var/www/vhosts/blabla.org/httpdocs/tmp/", $ttl=345600){ if(!is_dir($folder)){ $this->showError("Directory '" . $folder . "' does'nt exist"); }else{ $this->_cache = true; $this->_cache_folder = $folder; $this->_cache_ttl = $ttl; } return false; } } 

The original author gave me permission to post parts of the code here to solve this problem.


+4
source share
5 answers

If I understand the question correctly, this can be expected. Image manipulation is slow.

Yellow is your browser sending the request. Green is your browser, waiting on the server to actually create a thumbnail, which takes a very significant amount of time, regardless of which library the server uses. Blue is the server sending the response, which, unlike the previous steps, depends on the file size.

There is not much that can be done about the inherent slowness of image manipulation. It would be wise to cache these thumbnails so that they are generated only once and then statically set. Thus, very few of your users will ever sit through this green delay, and your server will be happy too.

EDIT: If the problem is that the files exist on these URLs, but your RewriteRule generally kicked, keep in mind that by default the rules run without checking if the file exists.

Use the following condition on a RewriteRule to verify that the file exists.

 RewriteCond %{REQUEST_FILENAME} !-f RewriteRule # ...etc... 
+5
source

imgcpu.php src = Foo / foo.jpg &? W = 100 & h = 100

so imgcpu.php run for every image request?

In this case, if you are concerned about performance,

  • The script needs to do some caching of the generated thumbnails. If he resizes the material for each request, this is your problem right there.

  • the script needs to send some caching headers to the browser - a pure PHP script will not do this and will be updated every time the page loads

  • a session_start() call inside a PHP script can lead to concurrency problems due to session blocking.

You will need to show some PHP code. Maybe in a separate issue.

+3
source

Apache can serve files from your hard drive much faster than PHP, and it seems that you are doing the latter to handle caching:

  /** * Get an image from cache * * @access public * @param string $name * @return void */ private function cachedImage($name){ $file = $this->_cache_folder . $name; $fh = fopen($file, 'r'); $content = fread($fh, filesize($file)); fclose($fh); return $content; } 

It is best to do what this function does ( passthru ), but the best option is to set up a regular expression that will overwrite the request only for your thumbnail script if the file does not already exist

 RewriteEngine On RewriteCond %{REQUEST_FILENAME} -s [OR] RewriteCond %{REQUEST_FILENAME} -l [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteRule ^images/.*$ - [NC,L] RewriteRule ^images/(.*)$ /imgcpu.php/$1 [NC,L] 

Then enter the logic to parse the image request and format it. For example, you can say that the thumbs should be named in the name of the source file and have dimensions W x H, added as "stackoverflow_logo_100x100.jpg".

Make sense?


In the request (in the comment), the description of the flags "s", "l" and "d" is as follows (citing documents):

'- d' (is a directory) Satisfies TestString as a path and tests whether it exists or not, and a directory.

'- s' (there is a regular file with a size) Satisfies TestString as a path and tests regardless of whether it exists or not, and a regular file larger than zero.

'- l' (symbolic link) TestString as a path and tests regardless of whether it exists or not, and a symbolic link.

+3
source

Checking your header and cache HTTP_IF_MODIFIED_SINCE AFTER you create the image so that the image is generated and cached every time the page loads. You will significantly reduce the time if you move these checks closer to the start of execution before you start processing the image.

+2
source

The match gave you an answer why. If you want to fix this, save the created thumbnails so that they are not recreated for each request. I use a simple 404 page that catches a request for thumbnails that have not been created, that the script displays the required sizes and the file from the url - for example, / thumbs / 100x100 / cat.png means creating a 100x100 thumbnail from / images / cat. PNG.

0
source

Source: https://habr.com/ru/post/1343373/


All Articles