LD
Change your colour scheme

Building a quick CDN with PHP

I’ve been using Bunny CDN (referral link) as my CDN for a while, and I’ve been really happy with it. In particular, the Image Optimizer is great value for money – $9.50 a month for on-the-fly dynamic image resizing and re-encoding. The only real problem is that my site isn’t particularly high-traffic. In the last […]

I’ve been using Bunny CDN (referral link) as my CDN for a while, and I’ve been really happy with it. In particular, the Image Optimizer is great value for money - $9.50 a month for on-the-fly dynamic image resizing and re-encoding.

The only real problem is that my site isn’t particularly high-traffic. In the last 30 days I’ve served a grand total of 13mb worth of requests; Instagram, I am not. So, I’m spending $10 a month for very little benefit for my current use case.

So, why not try making my own in PHP. Why PHP? Because PHP is nice to work in, deploying it is easy, and I already write too much Javascript at my day job.

The basic system is:

I’m using GdImage for image transformation, and to be honest that makes life pretty simple. Here’s the function for resizing an image to a given width:

public function toWidth(\GdImage $image, int $width): \GdImage
{
$rawWidth = imagesx($image);
$rawHeight = imagesy($image);

if ($width < $rawWidth) {

$ratio = $width / $rawWidth;
$height = intval(floor($rawHeight * $ratio));

return imagescale($this->image, $width, $height);
}
return $image;
}

If the requested width is greater than the image width, I don’t try to scale it - the original width is the maximum. Other than that, I just scale down the width and maintain the aspect ratio to avoid stretching.

Then to serve the image:

public function encode(\GdImage $image, int $type, int $quality): string
{
ob_start();

switch ($type) {
case IMAGETYPE_PNG:
imagepng($image, null, $quality);
break;
case IMAGETYPE_GIF:
imagegif($image);
break;
case IMAGETYPE_JPEG:
imagejpeg($image, null, $quality);
break;
case IMAGETYPE_WEBP:
imagewebp($image, null, $quality);
break;
}

$image_data = ob_get_contents();
ob_end_clean();

return base64_encode($image_data);
}

There are some other improvements I’ve made further along, such as serving WebP images if the user’s browser supports them. I then save the resulting base64 string in a database, ready to be served later. Right now it’s just a SQLite database - a filesystem or document store would be quicker and scale better, but in reality I’m serving about 15 images in total.

Here’s it serving with default params:

A Commodore 64 with 1942 loaded

And resized to 300px:

The same photograph at half width

And at a reduced quality:

The previous photograph at low quality

It’s not a perfect replication of the features Bunny offers, but it does all the things I needed it for.

Responses