nginx als dynamischer Image-Rescaler


NGINX bringt ja einen Adapter zu libgd mit (ngx_http_image_filter_module), der eine Skalierung auf kleinere Bildgrößen im Rahmen des Request-/Response-Zyklus ermöglicht. Leider ohne gutes Caching. Hier stelle ich eine Lösung vor, die einen Cache (mit nginx-Boardmitteln) für diese Bilder ermöglicht

Image Filter mit fester Skalierung

Standardbeispiel:

location ~ ^/.*\.jpg {
    image_filter scale 1000 1000;
}

Diese Konfiguration hat den Nachteil, dass sowohl die Bildgrößen fix sind als auch, dass der Filter bei jedem Request das Bild neu berechnet.

Wichtig: in der nginx.conf muss vor dem ersten Block (events oder http) das Modul geladen werden, falls nicht bereits statisch gelinkt. Dies betrifft z.B. das offizielle Docker-Image.

load_module /etc/nginx/modules/ngx_http_image_filter_module.so;

Image Filter mit Skalierung per Parameter

Nächster Schritt: beliebige Skalierungen via Parameter erlauben, mit Fallback auf die Originaldatei:

location / {
    ## gibt es den Parameter ?w=Zahl
    if ($arg_w ~ "\d+") {
        ## rewrite, so dass das skalierte Bild generiert wird
        ## geholt wird.
        ## Annahme: Alle skalierbaren Bilder liegen unter /media
        rewrite (?i)^(/media/.*\.jpg)$ /__scaler-cache/$1;
    }
    ## ... normale Angaben der location, wie bisher 
}

## Interne URL für die skalierten Bilder
location /__scaler {
    internal;
    ## Prefix wieder entfernen
    rewrite ^/__scaler/(.*)$ /$1 break;
    add_header X-Image-Scaling "$arg_w x 1280";
    ## Skalieren mit 
    image_filter resize $arg_w 1280;
    ## Maximale Größe des Originalbildes
    image_filter_buffer 50M;
}

Ein Bild, das via image.jpg?w=1280 geladen wird, wird nun automatisch auf eine maximale Höhe und Breite von jeweils 1280 Pixel skaliert. Leider noch ohne Caching.

Image Filter mit Skalierung per Parameter und Caching

Nginx unterstützt über ngx_http_proxy_module ein sehr gut einstellbaren Cache für die Verwendung als reverse-proxy. Zusammen mit den vorherigen Parts ergibt sich nun folgende Konfiguration:

proxy_cache_path /tmp/cache levels=1:2 keys_zone=img_cache:10m max_size=100m
                 inactive=30m use_temp_path=off;
#...
server {
    #...
    ## Cache für die skalierten Images
    location /__scaler-cache {
        internal;
        expires 6h;
        proxy_cache_methods GET HEAD;
        ## Erzwungene Reloads sollen kein Cache nutzen
        proxy_cache_bypass $cookie_nocache $arg_nocache $http_pragma;
        ## Name des caches
        proxy_cache img_cache;
        ## MISS/HIT/BYPASS Information
        add_header X-Image-Cache $upstream_cache_status;
        proxy_cache_valid 200 6h;
        proxy_cache_revalidate on;
        ## Weiterleitung zu der nachfolgenden `__scaler` konfiguration.
        proxy_pass http://127.0.0.1:80/__scaler;
    }

    location /__scaler {
        ## wird nur von __scaler_cache aufgerufen
        allow 127.0.0.1/32;
        deny all;
        ## Prefix wieder entfernen
        rewrite ^/__scaler/(.*)$ /$1 break;
        add_header X-Image-Scaling "$arg_w x 1280";
        ## Skalieren mit 
        image_filter resize $arg_w 1280;
        ## Maximale Größe des Originalbildes
        image_filter_buffer 50M;
    }

    location / {
        if ($arg_w ~ "\d+") {
            rewrite (?i)^(/media/.*\.jpg)$ /__scaler-cache/$1;
        }
        ## ...
    }
    ## ...
}

Mit ein paar maps und ifs lässt sich das auch noch auf einstellbare Breite und Höhe erweitern:

# Default für Breite und Höhe
map $arg_w $width {
    "" "-";
    default "$arg_w";
}
map $arg_h $height {
    "" "-";
    default "$arg_h";
}

server {
    #... wie oben
    location /__scaler {
        ## wird nur von __scaler_cache aufgerufen
        allow 127.0.0.1/32;
        deny all;
        ## Prefix wieder entfernen
        rewrite ^/__scaler/(.*)$ /$1 break;
        add_header X-Image-Scaling "$width x $height";
        ## Skalieren auf die angegebene maximale Größe bei Beibehaltung des Seitenverhältnis  
        image_filter resize $width $height;
        ## Maximale Größe des Originalbildes
        image_filter_buffer 50M;
    }
    location / {
        set $do_scale 0;
        if ($arg_w ~ "\d+") {
            set $do_scale 1;
        }
        if ($arg_h ~ "\d+") {
            set $do_scale 1;
        }
        if ($do_scale = 1) {
            rewrite (?i)^(/media/.*\.(jpg|png))$ /__scaler-cache/$1;
        }
        ## ...

    }
    ##...
}
Kommentare per Mail an post@wolfgang-jung.net.