
Для KinsburgTV нужно было сделать ресайз картинок. Сначала я скопировал с одного из своих проектов шаблонный тег с ресайзингом (берущий корни от этого сниппета), но для продакшена столь медленное решение казалось неприемлемым. Поэтому я написал другой шаблонный тег, который строит нужный URL для Nginx, и далее его обрабатывает ngx_http_image_filter_module и proxy_cache.
У меня есть график для сравнения, который я сделал через JMeter, но он к сожелению оказался неинформативен. Если кому всётаки интересно, то вот этот график. Слева вариант на PIL, справа Nginx. Выборка на 1000 реквестов.
Получился быстрый, прозрачный для приложения ресайзер и кроппер с возможностью кешировать результаты, а также с командой для конвертирования картинок в подходящий формат. Итак, встречайте django-nginx-image!
Прозрачный он, потому что нет необходимости подготавливать заранее все необходимые варианты эскизов (они изготавливаются по запросу и их не видно в каталогах проекта), а при смене размеров нужно это делать снова и снова. Я сторонник того чтобы хранить только оригиналы, а всякие кеши, денормализованные данные и прочее - являются только мерами по повышению производительности, но не частью логики проекта.
Если вас волнует вопрос перебора ширины и высоты в URL, то можно указать нужные правила для ограничения размеров непосредственно в Nginx.
Установка и настройка
Ставим с PyPI:
pip install django-nginx-image
Далее необходимо настроить Nginx (приведены настройки, относящиеся только к теме обсуждения):
- <STORAGE_ROOT> - путь до каталога с "media/statiс" (например, у меня это "/storage/kinsburg_tv");
- <CACHE_NAME> - произвольное имя для кеша (например, "kinsburg_tv_thumbnails_cache").
http { # Укажите необходимый путь до каталога кеша, имя кеша и максимальный размер кеша proxy_cache_path <STORAGE_ROOT>/nginx/cache levels=1:2 keys_zone=<CACHE_NAME>:10m max_size=1G; # Теперь настроим сервер, который будет кешировать результаты server { listen 80; server_name www.example.org; location ~* ^/(resize|crop)/ { proxy_pass proxy_cache <CACHE_NAME>; proxy_cache_key "$host$document_uri"; proxy_cache_valid 200 1d; proxy_cache_valid any 1m; proxy_cache_use_stale error timeout invalid_header updating; } } # И сервер, который будет выполнять resize и crop server { listen 80; server_name image.example.org; location ~* ^/resize/([\d\-]+)/([\d\-]+)/(.+)$ { alias <STORAGE_ROOT>/$3; image_filter resize $1 $2; image_filter_buffer 2M; error_page 415 = /empty; } location ~* ^/crop/([\d\-]+)/([\d\-]+)/(.+)$ { alias <STORAGE_ROOT>/$3; image_filter crop $1 $2; image_filter_buffer 2M; error_page 415 = /empty; } location = /empty { empty_gif; } } }
Если вам не нужно кеширование, то просто удалите локейшен из первого "server" и перенесите локейшены из второго "server" в первый. Кеширование мне дало прирост производительности на 10-20%.
Схема, иллюстрирующая работу:

Использование
В любом своём шаблоне подключите модуль nginx_image с шаблонным тегом thumbnail, передайте ему путь до картинки, ширину, высоту и опционально флаг crop, чтобы картинка обрезалась, а не ресайзилась:
{% load nginx_image %} Пропорциональный ресайз картинки, основанный на её ширине и высоте (доминирует высота): <img src="{% thumbnail user.profile.avatar 130 130 %}" /> Пропорциональный ресайз картинки, основанный на её ширине: <img src="{% thumbnail user.profile.avatar 130 '-' %}" /> <img src="{% thumbnail user.profile.avatar 130 0 %}" /> <img src="{% thumbnail user.profile.avatar 130 %}" /> Пропорциональный ресайз картинки, основанный на её высоте: <img src="{% thumbnail user.profile.avatar '-' 130 %}" /> <img src="{% thumbnail user.profile.avatar 0 130 %}" /> Обрезать картинку: <img src="{% thumbnail user.profile.avatar 130 130 crop=1 %}" /> <img src="{% thumbnail user.profile.avatar 130 0 crop=1 %}" /> <img src="{% thumbnail user.profile.avatar 0 130 crop=1 %}" />
В общем этот тег только и делает, что формирует URL вида:
- /resize/130/-/media/users/avatars/12345.jpg
- /resize/-/130/media/users/avatars/12345.jpg
- /crop/130/-/media/users/avatars/12345.jpg
Этот тег можно использовать вне шаблона, например:
class Film(models.Model): thumbnail = models.ImageField(upload_to='films/thumbnails', null=True) def thumbnail_preview_url(self): from nginx_image.templatetags.nginx_image import thumbnail return thumbnail(self.thumbnail, 160, 140, crop=True)
Команда nginx_image_converter
К сожалению (или к счастью), ngx_http_image_filter_module поддерживает только JPEG, GIF и PNG, поэтому пришлось писать конвертер, преобразующий форматы, отличные от вышеназванных, в JPEG.
./manage.py nginx_image_converter -i /storage/project/media -o /storage/project/newmedia
Дополнительные опции этой команды смотреть тут.
Проблема с Nginx resolver
Если вам встретилась примерно такая ошибка в nginx/error.log:
... no resolver defined to resolve image.example.org ...
то укажите resolver в nginx/nginx.conf, например так:
resolver 127.0.0.1 8.8.8.8;
Поддержка image_filter в Nginx
Чтобы добавить поддержку этого модуля в ваш Nginx, необходимо пересобрать Nginx. В Debian надо просто доустановить nginx-extras:
sudo apt-get install nginx-extras
Во FreeBSD надо пересобрать порт:
cd /usr/ports/www/nginx make config
Выбираем опцию HTTP_IMAGE_FILTER и переустанавливаем:
make deinstall && make install clean
Всё.
Toggle to English articles
Комментарии
Очень полезная библиотека, спасибо! Единственное, вот здесь в статье ошибка, долго искал в чем проблема - - надо $uri вместо $request_uri
Странно, почему у вас "$request_uri" не работает, это корректная переменная