2

In my site i am doing a image protection section to cut the costs of Amazon S3. So as a part of that i have made anti hot-linking links for images using php (to the best of my understanding).

<video src="/media.php?id=711/video.mp4"></video>

Then my media.php file looks like:

if (isset($_SERVER['HTTP_REFERER']) && $_SERVER['HTTP_REFERER'] != 'example.com')
{
  header('HTTP/1.1 503 Hot Linking Not Permitted');
    header("Content-type: image/jpeg");
    readfile("http://example.com/monkey.jpg");
  exit;
}

$url = 'https://s3.example.com';

$s3 = "$url/$file";

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $s3);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_NOBODY, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

$results = explode("\n", trim(curl_exec($ch)));
foreach($results as $line) {
        if (strtok($line, ':') == 'Content-Type') {
                $parts = explode(":", $line);
                $mime = trim($parts[1]);
        }
}

header("Content-type: $mime");
readfile($s3);

To make it less obvious, I have set up a rewrite to route /711/video.mp4 into cdn/711/video.mp4. That way, it doesn't look like there is an PHP script.

RewriteRule ^cdn/([0-9a-zA-Z_]+)/([0-9a-zA-Z-\w.-]+)([\w.-]+)?$ media\.php\?id=$1/$2 [QSA,L]

This above system is working fine but the issue is when i load image directly the loading time of the image is 237ms and when the image is loaded through this PHP script the loading time is 1.65s

I have shared the entire code i have, so if there is any chance of improvement in it please guide me in the right direction so i can make changes accordingly.

  • `$_SERVER['HTTP_REFERER']` is not reliably present. See: [How reliable is HTTP_REFERER?](https://stackoverflow.com/questions/6023941/how-reliable-is-http-referer) You may have shared all the code, but your talking about an image, whereas the route suggests a video, and why do you need to use cURL? – KIKO Software Aug 06 '19 at 14:17
  • I currently have both the images and videos on that server and the code i have shared above is for reference to understand what i am trying to do and the curl is used to get the mime type of the external file from the original S3 server where the images / videos are hosted. – Harry Callum Aug 06 '19 at 14:23
  • Why don't you get rid of the cURL totally, and just have two PHP scripts, one for images, the other for videos. They can, of course, share some code in a third file. – KIKO Software Aug 06 '19 at 14:26
  • I assume you're using the HTTP protocol, to read the file, because the PHP script is on another server than the images and videos. That explains the slowness. The images are passing through a second server, instead of coming from one. If the PHP script is on the same server, you should use a path to the file, not an URL. – KIKO Software Aug 06 '19 at 14:30
  • Why do you want to use S3 if you plan to tunnel all traffic through another server? That defeats the purpose of using a CDN – Nico Haase Aug 06 '19 at 14:35

1 Answers1

1

The reason your script takes longer than querying s3 directly is that you've added a lot of overhead to the image request. Your webserver needs to download the image and then forward it to your users. That is almost definitely your biggest bottleneck.

The first thing I would suggest doing is using the S3 API. This still uses curl() under the hood but has optimizations that should nominally increase performance. This would also allow you to make your s3 bucket "private" which would make obscuring the s3 url unnecessary.

All of that said, the recommended way to prevent hotlinking with AWS is to use cloudfront with referrer checking. How that is done is outlined by AWS here.

If you don't want to refactor your infrastructure, the best way to improve performance is likely to implement a local cache. At its most basic, that would look something like this:

    $cacheDir = '/path/to/a/local/directory/';
    $cacheFileName = str_replace('/', '_', $file);

    if (file_exists($cacheDir . $cacheFileName)){
        $mime = mime_content_type($cacheDir . $cacheFileName);
        $content = file_get_contents($cacheDir . $cacheFileName);
    } else {
        $url = 'https://s3.example.com';

        $s3 = "$url/$file";

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $s3);
        curl_setopt($ch, CURLOPT_HEADER, 1);
        curl_setopt($ch, CURLOPT_NOBODY, 1);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        $results = explode("\n", trim(curl_exec($ch)));
        foreach($results as $line) {
            if (strtok($line, ':') == 'Content-Type') {
                $parts = explode(":", $line);
                $mime = trim($parts[1]);
            }
        }
        $content = file_get_contents($s3);
        file_put_contents($cacheDir . $cacheFileName, $content);
    }

    header("Content-type: $mime");
    echo $content;

This stores a copy of the file locally so that the server does not need to download it from s3 every time it is requested. That should reduce your overhead somewhat, though it will not do as well as a purely AWS based solution. With this solution you'll also have to add ways of cache-breaking, periodically expiring the cache, etc. Just to reiterate, you shouldn't just copy/paste this into a production environment, it is a start but is more a proof of concept than production ready code.

TheGentleman
  • 2,324
  • 13
  • 17