JuangaCovas.info

La página personal de Juan Gabriel Covas

Herramientas de usuario

Herramientas del sitio


personal:codigo:lighttpd-cdn

Bash: CDN de imágenes con lighttpd

– Juan Gabriel Covas. 2014

El objetivo es tener uno o varios servidores que sirvan exclusivamente imágenes y liberen de carga los servidores al frente, usando subdominios cdn.* para múltiples dominios / webs. Además, cuando los administradores de las webs suban nuevas imágenes, el “CDN” debe ser capaz de recoger (pull) las imágenes faltantes “en tiempo real” y evitando dar un error 404, sirviéndolas en la misma petición.

Como solución simple montaremos lighttpd para servir las imágenes, haciendo que ejecute un script bash como CGI cuando no encuentre alguna imagen, descargándola del sitio original y sirviéndola después. En resumen que podemos primero pre-calentar las imágenes con rsync, las nuevas imágenes conforme se vayan subiendo se gestionarán via un 404-handler y a correr. Con el módulo evhost soportamos múltiples dominios/webs y podríamos escalar a varios servidores de nuestro CDN via DNS, geográficamente usando AWS.

Indicamos a lighttpd que ejecute los archivos .sh con /bin/bash

/etc/lighttpd/conf.d/cgi.conf

cgi.assign = ( ".pl"  => "/usr/bin/perl",
               ".cgi" => "/usr/bin/perl",
               ".rb"  => "/usr/bin/ruby",
               ".erb" => "/usr/bin/eruby",
               ".py"  => "/usr/bin/python",
               ".sh" => "/bin/bash" ) <---------

Aquí simplemente hacemos que los 404 invoquen nuestro script bash con la siguiente línea

server.error-handler-404   = "/cgi-bin/cdn-404-handler.sh"

/etc/lighttpd/conf.d/evhost.conf

# hosts that begin with "cdn."
$HTTP["host"] =~ "^cdn\." {

        # map the document-root using evhost path-pattern
        evhost.path-pattern        = vhosts_dir + "/cdn/%0/"

        # alias /cgi-bin from any "cdn." host to the unique cdn/cgi-bin/ directory
        alias.url = ( "/cgi-bin/" => "/srv/www/cdn/cgi-bin/" )

        # handle any 404 error with our special bash script
        server.error-handler-404   = "/cgi-bin/cdn-404-handler.sh"

        # also deny php
        url.access-deny            = ( "~", ".inc" )

        # deny /wp-admin/ urls
        $HTTP["url"] =~ "/wp-admin/" { url.access-deny = ("") }

        # deny any unexpected referer
        # $HTTP["referer"] !~ "($|domain1|domain2|domain3)" { url.access-deny = ("") }
}

El script .sh que se llama cuando se produce un 404 y con el que hacemos pull del archivo solicitado. Un cutre-CDN, por diversión…

/srv/www/cdn/cgi-bin/cdn-404-handler.sh

#!/bin/bash
#
### CDN Pull script for WPC7.com
### by Juanga 2014

# This script should be called as a 404 handler when a file is not found

#
# lighttpd config:
# server.error-handler-404   = "/cgi-bin/cdn-404-handler.sh"
# evhosts.conf:
## %0 => domain name + tld
### evhost.path-pattern        = vhosts_dir + "/cdn/%0/"
### alias.url = ( "/cgi-bin/" => "/srv/www/cdn/cgi-bin/" )
#

TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")

# just for testing:
# printenv > /tmp/cdn-404-handler-envvars.txt
# SERVER_NAME="cdn.testdomain.com"

logfile="/var/log/lighttpd/pull-cdn.other.log"

if [ -z $SERVER_NAME ] ;then
        echo "${TIMESTAMP} - ${REMOTE_ADDR} --> Unexpected host: http://${SERVER_NAME}${REQUEST_URI}" >>${logfile}
        printf 'HTTP/1.0 404 WTF! Unexpected host\n\n'
        exit 1
fi

logfile="/var/log/lighttpd/pull-${SERVER_NAME}.log"

regexp="^/wp-content/uploads/"
if [[ $REQUEST_URI =~ $regexp ]] ;then
        printf 'X-Action: pull\n'
else
        echo "${TIMESTAMP} - ${REMOTE_ADDR} --> Unexpected request: http://${SERVER_NAME}${REQUEST_URI}" >>${logfile}
        printf 'HTTP/1.1 404\n'
        printf 'Status: 404\n'
        printf 'Content-type: text/html\n'
        printf '\n'
        printf '<html><body><h1>404 WTF</h1>Unexpected file request for WPC7.com CDN http://%s</body></html>' ${SERVER_NAME}${REQUEST_URI}

        exit 1
fi

# set the pull host by changing cdn. to www.
PULL_HOST=`sed "s/cdn./www./g" <<< "${SERVER_NAME}"`
# split domain parts, assuming: cdn.domain.tld
IFS='.' read -a HOST_PARTS <<< "${SERVER_NAME}"
# set domain.tld
PULL_DOMAIN="${HOST_PARTS[1]}.${HOST_PARTS[2]}"

# try to get the http response code for the very same request at the pull host
response=$(curl --head --write-out %{http_code} --silent --output /dev/null http://${PULL_HOST}${REQUEST_URI})

echo "${TIMESTAMP} - ${REMOTE_ADDR} --> ${response} $PULL_HOST${REQUEST_URI} [${HTTP_REFERER}]" >>${logfile}

if [ $response -ne 200 ] ;then
        printf 'HTTP/1.0 404 Not Found\n'
        printf 'Status: 404\n'
        printf 'Content-type: text/html\n'
        printf '\n'
        printf '<html><body><h1>404 Not found, pull failed</h1>[%s] http://%s</body></html>' ${response} ${PULL_HOST}${REQUEST_URI}
        exit 1
fi

# try to download the file from the pull host
filename_saveas="/srv/www/cdn/${PULL_DOMAIN}${REQUEST_URI}"
wget -q -O ${filename_saveas} ${PULL_HOST}${REQUEST_URI}

if [ ! -f $filename_saveas ] ;then

        echo "    FAILED download, cannot stat ${filename_saveas}" >>${logfile}
else
        echo "    WRITTEN: ${filename_saveas}" >>${logfile}
        # ToDo: check that file is not empty (0 bytes)
        # sudo chown lighttpd:lighttpd ${filename_saveas}
        # sudo chmod 664 ${filename_saveas}
fi

# point to the pull URL for this request, next should find the file at cdn host
printf 'Location: http://%s%s\n' ${PULL_HOST} ${REQUEST_URI}
printf '\n'

exit 0

~~DISCUSSION|Comentarios~~

personal/codigo/lighttpd-cdn.txt · Última modificación: 10/07/2020 17:50 por Juanga Covas