공부하고

사용자에게 안정적인 속도로 콘텐츠를 제공하기 위해서 CDN(Contents Delivery Network) 서비스를 사용하곤 합니다.



이 서비스는 존재를 몰라 쓰지 못하는 경우도 있지만 비용적인 문제로 사용을 하지 못하는 경우가 상당합니다.

* 국내 CDN 서비스 중 저렴한 업체가 1TB 당 5만원 정도 꼴입니다.



3~4년 전에는 네임서버만 연결하면 무료로 CDN 서비스를 이용할 수 있는 CloudFlare가 국내에 알려지면서 개인 블로그나 사이트에서 사용을 하게 되었는데요.


하지만 국내 3사 통신사의 비싼 네트워크 비용 정책으로 인해 현재 CloudFlare에서는 수백만원에 이르는 Enterprise 플랜을 사용하지 않고서는 서울 PoP을 이용할 수 없게 하였고 대신 미국, 홍콩 등에 위치한 PoP으로 연결되어 오히려 전체적인 속도가 느려지는 문제가 발생해 더이상 사용하지 않는 서비스가 되었습니다.



그래서, 위와 같은 비용적인 문제를 해소하면서 CDN의 이점을 잡아보기 위해 CDN 서비스를 직접 만들어 보았습니다.




index.php : src 파라메터로 원격지 파일을 전달하면 $cache_dir에 해당 파일명으로 저장합니다.

* 참고 : 길호넷

<?php
$base_url = urldecode($_GET['src']);
if (!$base_url) return; // wrong source

$allowed_domains = ['example.com', 'www.example.com'];
$allowed_image_type = ['image/png', 'image/jpeg', 'image/gif', 'image/bmp'];

if (isset($_SERVER['HTTP_REFERER'])) {
    $referer = parse_url($_SERVER['HTTP_REFERER']);
    if (in_array($referer['host'], $allowed_domains) == false) {
        return; // wrong access
    }
}

$base_url = base64_decode($base_url);
$url = @parse_url($base_url);
$cache_dir = "/var/www/html/cache";

if (in_array($url['host'], $allowed_domains) == false) {
    return; // wrong host
}

switch ($_SERVER['REQUEST_METHOD']) {
    case 'GET':
        $filename = $cache_dir . basename($url['path']);
        $dirname = dirname($filename);

        if (is_file($filename) == false) {
            $ch = curl_init();
            $fp = fopen($filename, 'w');
            curl_setopt($ch, CURLOPT_URL, $base_url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_FILE, $fp);
            curl_exec($ch);
            fclose($fp);

            switch ($code = curl_getinfo($ch, CURLINFO_HTTP_CODE)) {
                case 200:
                    break;

                default:
                    touch($filename, $code);
            }
            curl_close($ch);
        }

        if (is_file($filename)) {
            $filetype = mime_content_type($filename);
            if (in_array($filetype, $allowed_image_type) == false) {
                @unlink($filename);
                return; // wrong extension
            }

            $lastmodified = filemtime($filename);
            $etag = md5_file($filename);

            header('Content-Type: ' . $filetype);
            header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 77760000) . ' GMT');
            header('Cache-Control: public, max-age=77760000');
            header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastmodified) . ' GMT');
            header('Etag: ' . $etag);
            header('Access-Control-Allow-Origin: *');

            if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
                if ($_SERVER['HTTP_IF_MODIFIED_SINCE'] == gmdate('D, d M Y H:i:s', $lastmodified) . ' GMT' || $_SERVER['HTTP_IF_NONE_MATCH'] == $etag) {
                    header('HTTP/1.0 304 Not Modified');
                    exit;
                }
            }

            if (strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) {
                if (in_array($filetype, array('text/plain', 'text/html', 'text/css', 'text/javascript', 'application/json', 'application/xml'))) {
                    ob_start('ob_gzhandler');
                }
            }

            $fp = fopen($filename, 'rb');
            fpassthru($fp);
            fclose($fp);

            exit;
        }

        break;

    case 'POST':

    case 'PUT':

    case 'DELETE':
        break;
}

header('HTTP/1.0 404 File Not Found.');
?>


delete.php : 일정 기간이 지난 파일은 CDN 서버에서 삭제되도록 합니다.

<?php
$cache_dir = "/var/www/html/cache";
$delete_date = 7;

recursive_file_delete($cache_dir);

function recursive_file_delete($dir) {
    if(is_dir($dir)) {
        if($dh = opendir($dir)) {
            while(($entry = readdir($dh)) !== false) {
                if($entry == '.' || $entry == '..') continue;
                $subdir = $dir.'/'.$entry;
                if(is_dir($subdir)) {
                    recursive_file_delete($subdir);
                } else {
                    if($entry == 'index.php') continue;
                    $sfile = $dir.'/'.$entry;
                    $mtime = @filemtime($sfile);
                    if(file_exists($sfile) && (time() - $mtime <= 24 * 60 * 60 * $delete_date)) continue;
                    @unlink($sfile);
                }
            }
            closedir($dh);
        }
    }
}



사용법 안내


1. CDN 서버 웹 root 경로에 index.php를 위치시킵니다.

2. 호스트 서버에서 이미지 경로를 CDN 웹서버 도메인으로 변경합니다. 예) http://cdn-server/?src=http://example.com/image.png

3. CDN 서버에서 cron job에 delete.php가 주기적으로 작동하도록 등록합니다.




이번에 만든 CDN 서비스는 단순히 네트워크를 분산시켜주는 기능 밖에 없습니다.


추후에는 콘텐츠를 요청한 지역과 지리적으로 실제 가까운 위치의 서버에서 콘텐츠를 전달받을 수 있는 구성을 만들어보도록 하겠습니다.