Генератор миниатюрок из Nginx-а

    Итак, сегодня мы соберём генератор миниатюрок на базе любимого народом веб-сервера — nginx-а. Что примечательно, сделаем мы это без единого гвоздя, т.е. без единой строчки кода, не считая конфигурации.

    Старая школа велит нам генерировать все необходимые миниатюрки сразу после загрузки базового изображения. Способ проверенный и надёжный, однако, мы живём в изменчивом новом мире, в котором нам может понадобится новый размер миниатюрок с утра в понедельник. Поэтому генерировать миниатюрки будем на лету, по запросу.

    Для дела нам понадобится ngx_http_image_filter_module, модуль, который умеет трансформировать изображения в форматах JPEG, GIF и PNG. Модуль стандартный, но по-умолчанию не собирается, поэтому перед сборкой нгинкса нужно добавить его:
    ./configure --with-http_image_filter_module
    Теперь ставим libgd способом, принятым у нас в системе, и собираем нгинкс. Кстати, при сборке из портов FreeBSD достаточно выполнить make config и выбрать нужный модуль в списке.

    Просто пережимка
    Для простоты будем считать, что у нас есть домен images.domain.ru, с которого выдаются картинки лежащие в /path/to/images. Запросы к /some/image.jpg будут выдаваться напрямую, к /120x90/some/image.jpg или /120x-/some/image.jpg будут жаться в миниатюрки (во втором случае идёт масштабирование только по ширине). В таком случае получаем:
    server {
        server_name images.domain.ru;
        root /path/to/images;

        if ($uri ~ ^/(\d+|-)x(\d+|-)/) {
            set $w $1;
            set $h $2;
        }

        location / {
        }

        location ~ ^/(?:\d+|-)x(?:\d+|-)/.*\.(?:jpg|gif|png)$ {
            rewrite ^/[\w\d-]+/(.*)$ /$1;
            image_filter resize $w $h;
            break;
        }
    }

    Что тут происходит? Сначала мы определяем размеры миниатюрки и устанавливаем соответствующие переменные. Затем выдаём статику и переходим к выдаче миниатюрок, рассмотрим второй location по-подробнее: rewrite перенаправляет url на настоящий файл, image_filter уменьшает полученную картинку, break предотвращает внутренний редирект с выходом из location. Последнее важно иначе нгинкс пройдёт весь путь ещё раз с уже переписанным URL и выдаст полную картинку.

    Можно добавить предопределённые размеры:
    if ($uri ~ ^/small/) {
        set $w 120;
        set $h 90;
    }

    location ~ ^/(?:\d+|-)x(?:\d+|-)|small/.*\.(?:jpg|gif|png)$ {
        ...

    Этим можно уже пользоваться если не жалко процессора, к слову на FreeBSD 8/Core 2 Quad Q9550 @ 2.83GHz/DDR2-800 удавалось пережимать 200 картинок в секунду (jpeg, 640x480 -> 150x150). Но, конечно, нам процессор жалко, поэтому будем пережатые картинки кэшировать.

    Пережимка с кэшированием
    Кэшированием в нгинксе занимается ngx_http_proxy_module, кэширует он только то, что проксирует, поэтому нам понадобиться извернутся — будем слушать два порта и проксировать сами себя:
    proxy_cache_path /path/to/cache levels=1:2 keys_zone=thumbs:10m inactive=24h max_size=5G;

    server {
        server_name images.domain.ru;

        location ~ ^/(?:\d+|-)x(?:\d+|-)|small/ {
            proxy_pass http://localhost:8081;
            proxy_cache thumbs;
            proxy_cache_valid  200      24h;
            proxy_cache_valid  404 415  1m;
        }

        location / {
            root /path/to/images;
        }
    }

    server {
        listen 8081;

        ... конфигурация пережималки ...
    }

    Готово, теперь на вышеупомянутой системе нгинкс выдаёт 15000 картинок в секунду, если всё из кэша, — чертовски быстро. Я не буду расписывать настройки, они неплохо расписаны в документации модуля. А полный конфиг можно взять тут.
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 42

      0
      А этот модуль направлено сохранять превью получается не умеет? Просто каждые 24ч вы все равно генерите новую превью.
        +3
        Так можно сделать через proxy_store
          0
          Точно, дочитал ман по нему как раз.
          0
          Можно поставить 1y
            0
            Можно сделать через proxy_store, можно поставить больше таймаут, это уже дело вкуса.
            А можно радоваться тому, что неиспользуемые миниатюрки сами уничтожаться через сутки.
            Всё зависит от того, чего мы хотим добиться
              0
              Так они полюбому будут удаляться из-за параметра max_size=5G
                0
                Если будешь использовать proxy_store то, не будут
                  0
                  Сори, имел ввиду, если выставить большой proxy_cache_valid и оставить max_size=5G.
            0
            В одном проекте тоже решили ресайзить картинки при отдаче. Но пошли другим путём: nginx отдаёт запрос к php, тот в свою очередь ресайзит с использованием imagemagick и отдаёт картинку в nginx на кеширование. Такой способ выбрали из-за больших возможностей imagemagick.
            Перед тем как выбрать ресайз при отдаче походил по форумам веб-кодеров с вопросом стоит ли такое делать. Нам рекомендовали хранить оригиналы и при добавлении нового формата просто все оригиналы переделывать под новый формат. Но немного прикинув решили всё же делать при отдаче.
            Кстати connect.ua тоже делает через imagemagick и при отдаче.
              0
              Вариант с проксированием своего сервера node.js тоже рассматривался, но решили, что возможностей ngx_http_image_filter_module нам хватит, а если не хватит, то можно модуль дописать.

              На самом деле большие возможности ImageMagick тут просто не нужны, нужен просто ресайз картинок, иногда ресайз с кропом, это модуль умеет.
              –2
              ddos'еры будут рады?
                –1
                С предопределенными размерами ддосерам — бой :)
                  0
                  Сервер приложений заддосить всё же проще. А тут нужен нетривиальный сценарий атаки + плюс довольно большое количество запросов.
                  +1
                  Вы учите плохому: if ($uri…
                  Для этого есть выделения в location'ах.
                    0
                    location-ов может быть больше одного, тогда придётся дублировать (один для image_filter resize, второй для crop, например). Кроме того, с $1 $2 были проблемы в location, поэтому я и вынес определение размеров наружу
                      0
                      Это не оправдывает внезапных сегфолтов и обрезаний скриптов у nginx.

                      А для избавления от $1 и $2 есть именованные location:
                      location ~* \/images\/resize_(?<img>[0-9a-f]*)_(?<w>[0-9]{1-4})_(?<h>[0-9]{1-4})\.(?<ext>(jpg|gif|png))
                      {
                          alias /images/$img.$ext;
                          image_filter resize $w $h;
                      }
                      

                      Красиво, удобно, понятно, не сломается.
                        0
                        Ой, Игорь уже написал это ниже :(
                          0
                          Каких сегфолтов и обрезаний скриптов? Всё работает чётко
                    0
                    Сервер по-русски называется энджиникс, а не нгинкс, вы бы лучше на nginx везде заменили.
                      0
                      дада. Пруф:
                      nginx [engine x] — это HTTP-сервер и почтовый прокси-сервер. Я начал разрабатывать nginx весной 2002 года, а осенью 2004 года вышел первый публично доступный релиз.
                      © sysoev.ru/nginx/
                        –5
                        нджинкс
                        0
                        о, как раз интересовало как знакомый сделал сервис с превьюшками (:
                          +8
                          Не нужно никаких if ($uri), rewrite'ов и break'ов:

                          location ~ ^/(\d+|-)x(\d+|-)/(.*\.(?:jpg|gif|png))$ {
                          alias /path/to/images/$3;
                          image_filter resize $1 $2;
                          }
                            –1
                            Можно и так.
                            Просто у меня несколько location-ов в таком случае /path/to/images/ придётся указывать в каждом.
                              +2
                              И это прекрасно. Нужно концентрировать всю логику обработки запроса в одном location'е, который видно на одном экране, а не размазывать по всему конфигу, который с течением времени будет только расти.
                              +1
                              Кстати, сейчас при отсутствии файла image_filter 404 ответ превращает в 415, кажется несколько нелогичным
                                +1
                                Потому что 404, равно как и 500, для фильтра — не картинка.
                                  0
                                  Я понимаю почему, но мне кажется это нелогичным. Ответ Not Modified этот модуль к примеру не фильтрует.
                                  И как отловить 404? Я использую вот такой, не самый элегантный способ:
                                  location ... {
                                      ...
                                      if (!-f $request_filename) {
                                          rewrite ^.*$ /notfound last;
                                      }
                                      image_filter resize $w $h;
                                      ...
                                  }

                                  location = /notfound {
                                      return 404;
                                  }
                                    +2
                                    error_page 404 /nofound;
                              0
                              Плохая идея ресайзить картинки с помощью асинхронного сервера.
                              Лучше передать ети обязаности на бекенд.
                                0
                                Если сервер больше ничем не занимается, то в общем всё равно. бекэнд или сам сервер сколько ядер столько и есть.
                                  0
                                  Это верно только в том случаи, если сервер отдает только картинки.
                                  А если еще и статику то нет
                                    0
                                    при особом желании можно запустить два нгинкса, задним жать передним кэшировать и выдавать статику. Писать какой-то кастомный бекэнд — напрашиваться на неприятности
                                +2
                                правильно произносится не нгинкс, а «engine x» (энджин икс) sysoev.ru/nginx/
                                  0
                                  Игорь не ругает даже за «тпштч».
                                  +2
                                  для того, чтобы желающие не могли себе нагенерировать картинки произвольного размера, можно воспользоваться модулем http_secure_link
                                    0
                                    а без него никак? чтобы один раз указать список доступных размеров и все остальные варианты отдавали 404
                                      0
                                      С готовым списком не так удобно.
                                      Надо будет в него добавлять когда понадобятся новые типоразмеры картинок, а так просто пеняешь ссылку и всё.
                                        0
                                        мне нужно для обойного сайта. там фиксированный набор разрешений и новые появляются не так часто.
                                          0
                                          тогда само собой, пример для фиксированного размера я написал
                                    +1
                                    Хороший модуль =) упоминал о нём с год назад
                                    однако тогда народ напроч отказывался использовать front-end для ресайза картинок, предпочитая складывать мусорную статику рядом с оригинальным размером.
                                    Хорошо, что народ отходит от этого =)

                                    p.s. как называть nginx по-русски личное дело каждого, верно? Тпштч, например, вообще можно было услышать из уст Игоря =)

                                    Only users with full accounts can post comments. Log in, please.