Изображения играют важную роль в любом веб‑приложении. Это могут быть элементы оформления интерфейса или основной контент сайта. В любом случае, перед разработчиками и администраторами стоит задача эффективной работы с картинками и в этой статье мы рассмотрим решения, которые реализуются веб‑сервером Angie.
Навигация по циклу
-
Настройка location в Angie. Разделение динамических и статических запросов.
-
Перенаправления в Angie: return, rewrite и примеры их применения.
-
Сжатие текста в Angie: статика, динамика, производительность.
-
Работа с картинками в Angie.
Видеоверсия
Для вашего удобства подготовлена видеоверсия этой статьи, доступна на Rutube, VKVideo и YouTube.
Адаптивная доставка изображений
Современные веб‑браузеры поддерживают множество форматов, решающих различные задачи. Также можно ввести классификацию этих форматов по признаку поддержки клиентами:
-
классические (полная поддержка) — JPEG, PNG, GIF;
-
новые (ограниченная поддержка) — WebP, AVIF.
Новые форматы сжатия изображений появились как побочный продукт видеокодеков VP8 (WebP) и AV1 (AVIF), при этом сохранив высокие коэффициенты сжатия. При использовании новых форматов изображений на сайте приходится сохранять совместимость со старыми клиентами, то есть использовать адаптивную доставку изображений. Для тех, которые поддерживают новый формат отдавать WebP или AVIF, а для остальных использовать классические форматы.
Реализовать адаптивную доставку можно с помощью HTML‑вёрстки, где браузер будет выбирать поддерживаемый формат самостоятельно. Такой способ потребует изменения кода страниц и поддержки со стороны веб‑приложения.
Другой способ — использовать веб‑сервер и HTTP‑заголовок Accept, в котором указываются поддерживаемые форматы. В этом варианте код страниц не меняется, при этом клиенты будут получать различные форматы в зависимости от заявленной поддержки в заголовке Accept. При этом на диске уже должны находиться версии картинок в новых форматах, отличаться они будут дополнением в имени. Например, рядом с 1.jpg мы положим 1.jpg.avif. Именно этот способ мы реализуем в этой части статьи.
Первая часть решения: определить поддержку форматов через заголовок Accept. Для этого используем определение переменных через директиву map:
http { map $http_accept $webp_suffix { "~*webp" ".webp"; } map $http_accept $avif_suffix { "~*avif" ".avif"; "~*webp" ".webp"; }}
В этой части конфигурации мы создаём переменные $webp_suffix и $avif_suffix. Их значения соответствуют дополнительным расширениям файлов, которые мы будем размещать рядом с оригинальными картинками.
Вторая часть конфигурации: настройка локации для адаптивной доставки картинок. Здесь рекомендуется выделить отдельный блок именно для нужных форматов изображений. Например, это можно сделать с помощью регулярного выражения:
location ~* \.(jpe?g|gif|png)$ { try_files $uri$avif_suffix $uri$webp_suffix $uri =404; add_header Cache-Control "max-age=31536000, public, no-transform, immutable";}
В примере выше мы выделяем локацию для обслуживания файлов с расширениями jpg, jpeg, gif и png. Основную часть работы выполняет директива try_files, которая использует переменные $webp_suffix и $avif_suffix для дополнения имени файла (.webp,.avif). В случае нахождения файла на диске отдаётся новая версия, иначе — обычный вариант. Вторая директива (add_header) отвечает за клиентское кэширование ресурса, подробнее этот вопрос разобран в отдельной статье. Продемонстрируем работу этого блока псевдокодом.
$ ls1.jpg 1.jpg.avif 1.jpg.webp# Client>GET /1.jpg>Accept: webp,avif,*/*# Angie< $webp_suffix = ‘.webp’;< $avif_suffix = ‘.avif’;< try_files 1.jpg.avif 1.jpg.webp 1.jpg =404;< send 1.jpg.avif
Клиент получит содержимое файла 1.jpg.avif с корректным заголовком Content‑Type (если он заранее был прописан в файле /etc/angie/mime.types). С точки зрения HTML‑вёрстки никаких изменений не требуется (тэг картинки может выглядеть как <img src="1.jpg">).
Стоит заметить, что такая схема хорошо подходит для прямой работы с клиентами. Если между Angie и клиентом работает кэширующий слой (например, CDN), то стоит проверить корректность работы схемы и возможно стоит добавить заголовок Vary: Accept, который проинструктирует кэширующий сервер о зависимости ответа от заголовка запроса Accept.
Пока мы занимались только доставкой готовых картинок, но веб‑сервер Angie способен обрабатывать изображения самостоятельно. Начнём с возможностей масштабирования изображений.
Масштабирование, обрезка и поворот картинок
Работа с изображениями в Angie реализована в модуле Image Filter. Модуль собран динамически и поставляется в отдельном пакете. Установка:
apt install angie-module-image-filter
Подключение модуля (в контексте main):
load_module modules/ngx_http_image_filter_module.so;
Модуль опирается на библиотеку libgd, то есть поддержка форматов ограничена возможностями установленной в системе библиотеки (при использовании установки из пакетов). Например, в Ubuntu 24.04 библиотека поддерживает WebP, но не поддерживает AVIF. Модуль Image Filter также имеет поддержку формата HEIC, но его крайне слабая поддержка браузерами (только Apple‑системы) делает его бесполезным для веб‑разработки.
Первое, что позволяет модуль, это масштабирование картинок на лету. Такой подход позволяет не нарезать все требуемые версии картинок заранее, а использовать одну версию изображения на диске. Конечно, преобразование картинок — затратная операция для процессора, поэтому позже мы будем обсуждать возможности кэширования результатов.
Для динамического масштабирования картинок достаточно создать локацию для картинок с директивой image_filter и сопутствующими параметрами.
server { listen 80; root /var/www/image; location ~* ^/img/(?<width>\d+|-)x(?<height>\d+|-)/(?<file>.+\.jpe?g)$ { alias /var/www/image/img/$file; image_filter resize $width $height;image_filter_jpeg_quality 80;image_filter_interlace on;image_filter_sharpen 20;error_page 415 = /empty; } location = /empty { empty_gif; }}
В этом примере мы используем локацию с регулярным выражением и сохранением частей URI в переменные ($width, $height и $file). Данный блок обслуживает только файлы типа JPEG, производит масштабирование по параметрам ширины ($width) и высоты ($height), уровень качества при компрессии установлен в 80 (image_filter_jpeg_quality). Также указана настройка создавать прогрессивную версию файла (image_filter_interlace) и добавить увеличение резкости (image_filter_sharpen). Для того, чтобы сервер нашел картинку на диске, используется директива alias с путём к исходному файлу. Для обработки ошибки 415 (Unsupported Media Type) создана локация, возвращающая пустой однопиксельный GIF.
Тестовые картинки должны быть расположены в директории /var/www/image/img/. Чтобы получить масштабированную версию можно отправлять запросы вида:
GET /img/210x210/1.jpgGET /img/210x-/1.jpgGET /img/-x210/1.jpg
Первый запрос определяет одновременно и ширину и высоту картинки. В этом случае будет использоваться пропорциональное масштабирование (как и всегда) и картинка будет «вписана» в указанный прямоугольник. Во втором запросе указана только ширина, а в третьем только высота.
Максимальный размер картинок, с которыми может работать модуль, определяется директивой image_filter_buffer и по умолчанию равен 1 MБ.
Аналогичная конфигурация для lossless‑форматов (PNG, GIF):
location ~ ^/img/(?<width>\d+)x(?<height>\d+|-)/(?<file>.+\.(png|gif))$ { alias /var/www/image/img/$file; image_filter resize $width $height; image_filter_interlace on; image_filter_sharpen 20; error_page 415 = /empty;}
Стоит заметить, что такая конфигурация даёт максимальную гибкость в выборе размеров картинок, но может стать целью DoS‑атаки на сервер, когда злоумышленник будет запрашивать большое количество картинок с произвольными размерами. В качестве защиты рекомендуется создать локации с фиксированными параметрами размеров и использовать кэширование.
Кроме масштабирования, модуль позволяет обрезать (crop) изображения. Конфигурация аналогична первой:
location ~ ^/img/(?<width>\d+|-)x(?<height>\d+|-)/(?<file>.+\.jpe?g)$ { alias /var/www/image/img/$file; image_filter crop $width $height; image_filter_jpeg_quality 80; image_filter_interlace on; image_filter_sharpen 20; error_page 415 = /empty;}
Преобразование crop будет масштабировать картинку по большей стороне и далее обрезать лишние края, используя второй параметр размера. Например, для портретной ориентации картинки (1000×1500 px) при запросе размеров 400×400 сначала применится масштабирование до 400 по ширине, а затем обрезка по высоте до 400 пикселей (останется центральная часть изображения).
Также модуль умеет вращать картинки на углы, кратные 90. Для этого можно использовать параметр rotate директивы image_filter. При этом поворот можно использовать совместно с масштабированием или обрезкой:
location ~ ^/img/(?<width>\d+|-)x(?<height>\d+|-)/(?<file>.+\.jpe?g)$ { alias /var/www/image/img/$file; image_filter resize $width $height; image_filter rotate 90; image_filter_jpeg_quality 80; image_filter_interlace on; image_filter_sharpen 20; error_page 415 = /empty;}
В примере выше поворот на 90 градусов против часовой стрелки происходит после изменения размеров, в то время как при обрезке (crop) поворот происходит до изменения размеров.
Также модуль умеет проверять формат изображения (действие test) и возвращать данные о размере картинки (действие size).
Итак, мы научились изменять размеры, обрезать и поворачивать изображения, пора поговорить о вопросах оптимизации.
Конвертация и оптимизация
В начале статьи мы упоминали, что модуль Image Filter в Angie поддерживает различные форматы (при соответствующей поддержке libgd): JPEG, WebP, AVIF, HEIC. В отличие от модуля в Nginx, в Angie добавлена возможность конвертирования изображений между форматами. Это даёт интересные возможности по созданию системы оптимизации изображений средствами веб‑сервера.
Первый сценарий: конвертировать изображения из старых форматов в новые. Конечно, здесь нам пригодятся приёмы из адаптивной доставки изображений в первой части статьи. То есть сначала мы должны определить, поддерживает ли клиент нужный формат и в случае поддержки дать ему оптимизированную версию.
Определим префиксы для новых форматов на основе заголовка запроса Accept:
http { map $http_accept $webp_prefix { "~*webp" "-webp"; } map $http_accept $avif_prefix { "~*avif" "-avif"; "~*webp" "-webp"; }}
Теперь немного изменим структуру серверов в Angie. Первый будет отвечать за фронтенд, а второй заниматься обработкой картинок. Конфигурация фронта:
server { listen 443 ssl; root /var/www/image; rewrite ^/img(?<file>/.+)$ /img${webp_prefix}$file last; location ~ ^/img.+/.+\.(jpe?g|png|gif)$ { add_header Vary Accept; expires max; error_page 404 = @resize; } location @resize { add_header Vary Accept; expires max; proxy_pass http://127.0.0.1:8081$uri; }}
Это сервер, который будет отдавать изображения напрямую, если они есть на диске, либо проксировать на другой сервер (http://127.0.0.1:8081) для конвертации. Обратите внимание на директиву rewrite: она позволит перенаправить запрос внутри сервера на WebP‑версию при поддержке браузером.
Теперь конфигурация сервера для обработки картинок:
server { listen 8081; root /var/www/image; location ~ ^/img-webp/(?<file>.+)$ { alias /var/www/image/img/$file; image_filter convert webp; image_filter_webp_quality 80; error_page 415 = /empty; }}
Теперь при запросе картинки JPEG мы получаем конвертированное в формат WebP изображение:
curl --head -H "Accept: image/webp" https://localhost/img/1.jpgHTTP/2 200 server: Angie/1.11.4...content-type: image/webpvary: Accept
Аналогичная конфигурация по конвертации изображений для формата AVIF:
location ~ ^/img-avif/(?<width>\d+)x(?<height>\d+|-)/(?<file>.+)$ { alias /var/www/image/img/$file; image_filter convert avif; image_filter resize $width $height; image_filter_avif_quality 80 6; error_page 415 = /empty;}
Можно заметить, что при изменении формата используется режим convert директивы image_filter. Качество сжатия задаётся с помощью директив image_filter_avif_quality и image_filter_webp_quality.
Процесс конвертации можно совмещать с изменением размеров, например для WebP:
location ~ ^/img-webp/(?<width>\d+)x(?<height>\d+|-)/(?<file>.+)$ { alias /var/www/image/img/$file; image_filter convert webp; image_filter resize $width $height; image_filter_webp_quality 80; image_filter_sharpen 20; error_page 415 = /empty;}
Процесс обработки и конвертации изображений создаёт значительную нагрузку на процессор сервера, поэтому следующий логичный шаг в нашей системе — кэширование результатов для снижения нагрузки на сервер.
Кэширование результатов
Сохранять результаты обработки картинок модулем Image Filter можно двумя способами: классическое серверное кэширование proxy_cache и сохранение результатов на диск с помощью proxy_store. Начнём со второго метода.
Для использования proxy_store необходимо модифицировать конфигурацию фронтенд‑части нашего сервера, а именно локацию с проксированием:
location @resize { add_header Vary Accept; expires max; proxy_pass http://127.0.0.1:8081$uri; proxy_store/var/www/image$uri;}
Теперь при запросе картинки для конвертации в WebP:
curl --head -H "Accept: image/webp" https://localhost/img/1.jpg
Результат конвертации будет сохранён в виде файла по адресу /var/www/image/img‑webp/1.jpg. Если запросить изменение размеров:
curl --head -H "Accept: image/webp" https://localhost/img/100x100/1.jpg
То результирующий файл будет сохранён по следующему пути: /var/www/image/img-webp/100x100/1.jpg. Повторные запросы к этим версиям картинок будут обработаны как статические, без дополнительных затрат процессора.
Таким образом, мы решили проблему повторной генерации версий и снизили нагрузку. Но что будет, если сохранённые версии будут больше не нужны? С таким подходом — только ручная чистка и самостоятельное обслуживание хранилища картинок.
Второй подход позволяет гибко настраивать параметры кэширования версий и избавляет от необходимости ручного управления. Здесь мы будем использовать серверное кэширование. Для начала определим общие параметры кэширования (зону, ключ и время жизни):
http { proxy_cache_path/var/local/angie/cache levels=1:2 inactive=30d keys_zone=one:10m:file=/etc/angie/cache.state max_size=1G;}
Итак, мы создали зону кэширования под названием one, определили размер разделяемой памяти (10m) и максимальный размер на диске (max_size=1G). Для ускорения загрузки статуса кэша добавлен файл с сохранением состояния (file).
Теперь внесём изменения в конфигурацию проксирующего location:
location @resize { add_header Vary Accept; expires max; proxy_pass http://127.0.0.1:8081$uri; proxy_cache one; proxy_cache_key "$host$uri"; proxy_cache_lock on; proxy_cache_min_uses 2; proxy_ignore_headers "Cache-Control" "Expires"; proxy_cache_use_stale updating error timeout invalid_header http_500 http_502 http_504; proxy_cache_background_update on; proxy_cache_valid 200 14d; proxy_cache_valid any 1m;}
В новой версии конфигурации мы включили использование кэша в зоне one, определили ключ кэширования. Результат будет кэшироваться после двух запросов, чтобы не засорять диск редкими вариантами картинок (proxy_cache_min_uses). Также мы разрешили использовать старые версии картинок при обновлении кэша или в случае проблем у сервера. Время актуальности кэша ограничено 14 днями. Также будут кэшироваться ответы с ошибками. Эта конфигурация всего лишь показывает пример реализации задачи, конкретные значения нужно подбирать исходя из требований приложения.
Теперь у нас на вооружении два метода сохранения результатов преобразования картинок, используйте тот, который вам больше подходит.
Итоги
Подведём итоги по работе с картинками. Веб‑сервер Angie обладает широкими возможностями для работы с изображениями. Первый метод адаптивной доставки задействует лишь механизм переменных и директиву try_files, оставляя всю работу с изображениям за кадром. Модуль Image Filter напротив, берёт работу по изменению размеров и конвертации форматов на себя, избавляя от необходимости предварительной обработки картинок. Выбор подхода к работе с изображениями остаётся за читателем: либо подробная оптимизация и минимальная нагрузка на сервер с предварительно обработанными картинками, либо динамическая нарезка картинок без усложнения веб‑приложения.
ссылка на оригинал статьи https://habr.com/ru/articles/1028514/