immerse: Модуль фильтра современного формата изображений для NGINX
Установка Debian/Ubuntu
Эти документы относятся к пакету APT nginx-module-immerse, предоставляемому репозиторием GetPageSpeed Extras.
- Настройте репозиторий APT, как описано в настройке репозитория APT.
- Установите модуль:
sudo apt-get update
sudo apt-get install nginx-module-immerse
Показать дистрибутивы и архитектуры
| Дистрибутив | Версия | Компонент | Архитектуры |
|-------------|-------------------|-------------|-----------------|
| debian | bookworm | main | amd64, arm64 |
| debian | bookworm-mainline | main | amd64, arm64 |
| debian | trixie | main | amd64, arm64 |
| debian | trixie-mainline | main | amd64, arm64 |
| ubuntu | jammy | main | amd64, arm64 |
| ubuntu | jammy-mainline | main | amd64, arm64 |
| ubuntu | noble | main | amd64, arm64 |
| ubuntu | noble-mainline | main | amd64, arm64 |
Модуль фильтра NGINX для прозрачной доставки современных форматов изображений. Перехватывает
ответы с изображениями из любого источника (статические файлы, proxy_pass, FastCGI и т.д.)
и конвертирует их в WebP или AVIF на основе заголовков Accept клиента. Никакой перезаписи URL,
никакой отдельной службы, никаких изменений в приложении.
Как это работает
ngx_immerse вставляется в цепочку фильтров NGINX. Когда ответ с
Content-Type: image/jpeg, image/png или image/gif проходит через фильтр, модуль проверяет заголовок Accept клиента на поддержку современных форматов. Если совпадение найдено, он либо предоставляет кэшированное преобразование, либо инициирует его через пул потоков - сохраняя рабочие процессы не блокирующими.
Client Request NGINX
| |
|--- GET /photo.jpg ---------> |
| Accept: image/avif, |
| image/webp |
| |--- upstream / static file
| |<-- image/jpeg response
| |
| [ngx_immerse]
| |--- cache hit? serve cached avif
| |--- cache miss + lazy? serve jpeg,
| | queue background conversion
| |--- cache miss + sync? convert in
| | thread pool, serve avif
| |
|<-- 200 image/avif ---------- |
| Vary: Accept |
Особенности
- Прозрачная Negotiation форматов - анализирует заголовок
Acceptв соответствии с RFC 7231 с поддержкой коэффициента качества (q=0отклоняет, наивысшийqвыигрывает) - Вывод в WebP и AVIF - настраиваемое качество, порядок приоритета и условная компиляция (соберите только один, если это необходимо)
- Кэш на основе файлов - ключи MD5 с временем изменения источника в ключе, поэтому кэш автоматически недействителен, когда оригинальное изображение изменяется
- Два режима преобразования -
lazy(сначала предоставить оригинал, преобразовать в фоне) иsync(преобразовать встроенно, сразу предоставить современный формат) - Интеграция с пулом потоков - преобразования выполняются в пулах потоков NGINX, освобождая цикл событий
- Мягкая резервная копия - сбой преобразования, поврежденные изображения, отсутствующий пул потоков, полный диск: всегда предоставляет оригинал, никогда не возвращает 500
- Пороговые размеры - пропускает изображения ниже
immerse_min_sizeили вышеimmerse_max_size, чтобы избежать излишней нагрузки на процессор из-за крошечных значков или огромных ресурсов - Безопасность CDN - добавляет
Vary: Accept, чтобы кэши и CDN не предоставляли неправильный формат неправильному клиенту - Отладочный заголовок -
X-Immerse: hit|miss|error|passпоказывает, что произошло (включаемый) - Обнаружение магических байтов - определяет формат входных данных по сигнатуре файла, а не по расширению URL
Конфигурация
Минимальный пример
thread_pool immerse threads=4;
http {
immerse_cache_path /var/cache/nginx/immerse levels=1:2 max_size=1g;
server {
listen 80;
location /images/ {
immerse on;
immerse_thread_pool immerse;
alias /var/www/images/;
}
}
}
С проксируемым контентом
thread_pool immerse threads=4;
http {
immerse_cache_path /var/cache/nginx/immerse levels=1:2 max_size=1g;
server {
listen 80;
location /api/photos/ {
immerse on;
immerse_mode sync;
immerse_thread_pool immerse;
proxy_pass http://backend;
}
}
}
Полный пример со всеми директивами
thread_pool immerse threads=4;
http {
immerse_cache_path /var/cache/nginx/immerse levels=1:2
max_size=2g inactive=60d;
# По умолчанию для всех местоположений
immerse_formats avif webp;
immerse_webp_quality 82;
immerse_avif_quality 63;
server {
listen 80;
# Статические изображения - ленивый режим (по умолчанию)
location /images/ {
immerse on;
immerse_thread_pool immerse;
immerse_min_size 2k;
immerse_max_size 5m;
alias /var/www/images/;
}
# Проксированные изображения - синхронный режим для немедленной конверсии
location /api/photos/ {
immerse on;
immerse_mode sync;
immerse_thread_pool immerse;
proxy_pass http://backend;
}
# Только WebP (без AVIF)
location /thumbnails/ {
immerse on;
immerse_formats webp;
immerse_webp_quality 75;
immerse_thread_pool immerse;
alias /var/www/thumbs/;
}
# Отключить заголовок отладки в продакшене
location /cdn/ {
immerse on;
immerse_x_header off;
immerse_thread_pool immerse;
alias /var/www/cdn/;
}
}
}
Справочник по директивам
immerse
Синтаксис: immerse on | off;
По умолчанию: off
Контекст: location
Включает или отключает преобразование формата изображения для указанного местоположения. При включении модуль перехватывает ответы с изображениями и пытается преобразовать их в зависимости от поддержки клиентом.
Требует, чтобы immerse_cache_path был установлен на уровне http. Если путь к кэшу не настроен, модуль регистрирует ошибку и передает ответ без изменений.
immerse_cache_path
Синтаксис: immerse_cache_path path [levels=levels] [max_size=size] [inactive=time];
По умолчанию: не установлен (требуется при включенном immerse)
Контекст: http
Устанавливает каталог кэша и параметры. Эта директива обязательна - модуль не будет преобразовывать изображения без настроенного пути к кэшу.
Параметры:
- path - директория файловой системы для кэшированных преобразований. Создается автоматически, если не существует.
- levels - глубина иерархии подкаталогов, указанных в виде разделённых двоеточием
цифр (1 или 2). По умолчанию:
1:2. Приlevels=1:2ключ кэшаa3b1c4d5e6...хранится по путиpath/a/3b/a3b1c4d5e6....webp. - max_size - максимальный общий размер кэша. Принимает суффиксы размера (
k,m,g). По умолчанию: не установлено (без ограничения). - inactive - время, после которого неиспользуемые кэшированные файлы могут быть
удалены. Принимает суффиксы времени (
s,m,h,d). По умолчанию:30d.
immerse_cache_path /var/cache/nginx/immerse levels=1:2 max_size=1g inactive=30d;
immerse_formats
Синтаксис: immerse_formats format ...;
По умолчанию: avif webp
Контекст: http, server, location
Устанавливает предпочтительные форматы вывода в порядке приоритета. Когда несколько форматов принимаются клиентом с равными коэффициентами качества, побеждает первый формат, указанный здесь.
Действительные форматы: avif, webp. По крайней мере один из них должен поддерживаться во время компиляции.
Предпочитайте WebP над AVIF
immerse_formats webp avif;
WebP только
immerse_formats webp;
##
### immerse_mode
**Синтаксис:** `immerse_mode lazy | sync;`
**По умолчанию:** `lazy`
**Контекст:** `location`
Устанавливает стратегию преобразования для пропущенных кэшей.
**lazy** - сразу же обслуживает оригинальное изображение без задержки. Если
источник изображения основан на файле, ставит в очередь фоновое преобразование в пуле потоков. Преобразованный вариант доступен для последующих запросов. Лучше всего подходит для статической раздачи файлов, где важна задержка первого запроса.
**sync** - буферизует весь ответ, преобразует его в пуле потоков и обслуживает преобразованное изображение в том же запросе. Рабочий не блокируется (пул потоков обрабатывает работу). Лучше всего подходит для проксируемого контента или когда вы хотите, чтобы каждый ответ был в современном формате.
```nginx
Статические файлы - ленивый режим хорош, кеш быстро разогревается
location /images/ { immerse on; immerse_mode lazy; }
API ответы - синхронный режим обеспечивает современный формат при первом запросе
location /api/photos/ { immerse on; immerse_mode sync; proxy_pass http://backend; }
##
### immerse_webp_quality
**Синтаксис:** `immerse_webp_quality quality;`
**По умолчанию:** `80`
**Контекст:** `http`, `server`, `location`
Качество кодирования WebP (1-100). Более высокие значения обеспечивают лучшее визуальное качество при
больших размерах файла. Значения около 75-85 обеспечивают хороший баланс для большинства
контента.
##
### immerse_avif_quality
**Синтаксис:** `immerse_avif_quality quality;`
**По умолчанию:** `60`
**Контекст:** `http`, `server`, `location`
Качество кодирования AVIF (1-100). AVIF достигает хорошего визуального качества при более низких
числовых значениях, чем WebP или JPEG. Значения около 50-70 типичны для веб
доставки. Кодировщик использует скорость 6 (сбалансированная скорость/качество).
##
### immerse_min_size
**Синтаксис:** `immerse_min_size size;`
**По умолчанию:** `1k` (1024 байта)
**Контекст:** `http`, `server`, `location`
Минимальный размер тела ответа для конвертации. Изображения меньше этого проходят
без изменений. Это позволяет избежать чрезмерных затрат процессора на крошечные изображения (значки, 1x1
отслеживающие пиксели), где конвертация формата приносит незначительную экономию.
##
### immerse_max_size
**Синтаксис:** `immerse_max_size size;`
**По умолчанию:** `10m` (10485760 байтов)
**Контекст:** `http`, `server`, `location`
Максимальный размер тела ответа для конвертации. Изображения больше этого проходят
без изменений. Это предотвращает исчерпание ресурсов из-за очень больших изображений,
которые потребляют значительное количество памяти и процессорного времени во время декодирования/кодирования.
##
### immerse_thread_pool
**Синтаксис:** `immerse_thread_pool name;`
**По умолчанию:** `default`
**Контекст:** `http`, `server`, `location`
Имя пула потоков NGINX, который будет использоваться для задач конвертации. Должно совпадать с
директивой `thread_pool` в основном контексте конфигурации.
```nginx
## Определите выделенный пул
thread_pool immerse threads=4;
http {
server {
location /images/ {
immerse on;
immerse_thread_pool immerse;
}
}
}
Руководство по размеру: начните с количества ядер процессора. Кодирование изображений зависит от ЦП, поэтому больше потоков, чем ядер, не дает выгоды. Если тот же сервер обрабатывает другую работу в пуле потоков (aio), рассмотрите возможность выделенного пула для immerse.
immerse_x_header
Синтаксис: immerse_x_header on | off;
По умолчанию: on
Контекст: http, server, location
Контролирует заголовок ответа X-Immerse. Когда включен, каждый обработанный
ответ включает заголовок, указывающий на то, что произошло:
| Значение | Значение |
|---|---|
hit |
Обслужено из кэша |
miss |
Промах кэша; сконвертировано (синхронно) или оригинал обслужен (лениво) |
error |
Ошибка конверсии; оригинал обслужен как запасной вариант |
Отключите это в продакшене, если не хотите раскрывать внутреннее состояние модуля клиентам.
Заголовки ответа
Когда ngx_immerse обрабатывает ответ, он изменяет или добавляет следующие заголовки:
| Заголовок | Значение | Когда |
|---|---|---|
Content-Type |
image/webp или image/avif |
Преобразован или обслуживается из кеша |
Content-Length |
Размер преобразованного изображения | Преобразован или обслуживается из кеша |
Vary |
Accept |
Всегда (даже при прямой передаче), когда модуль активен |
X-Immerse |
hit, miss или error |
Когда immerse_x_header включен |
Заголовок Vary: Accept критически важен для корректного поведения CDN. Без него
CDN может кешировать ответ WebP и передавать его клиенту, который поддерживает только
JPEG.
Разбор заголовка Accept
Модуль разбирает заголовок запроса Accept в соответствии с RFC 7231:
- Извлекает записи
image/webpиimage/avifс их коэффициентами качества q=0означает, что клиент явно отклоняет этот форматq=1(или отсутствует параметрq) означает полную поддержку- Формат с наивысшим значением
qвыбирается - При равных значениях
qвыигрывает первый формат вimmerse_formats - Если ни один из форматов отсутствует или оба имеют
q=0, подается оригинал
Примеры:
| Заголовок Accept | Результат (с настройкой по умолчанию immerse_formats avif webp) |
|---|---|
image/avif, image/webp |
AVIF (первый в конфигурации, равные q) |
image/webp |
WebP |
image/avif;q=0.8, image/webp;q=0.9 |
WebP (более высокое q) |
image/avif;q=0, image/webp |
WebP (AVIF отклонен) |
text/html, image/jpeg |
Оригинал (нет современного формата) |
| ## Обнаружение формата входных данных |
Исходные изображения определяются по магическим байтам в теле ответа, а не по расширению файла:
| Формат | Магические байты |
|---|---|
| JPEG | FF D8 FF |
| PNG | 89 50 4E 47 0D 0A 1A 0A |
| GIF | GIF87a или GIF89a |
Изображения, уже находящиеся в формате WebP или AVIF, передаются без изменений. Анимированные GIF (несколько кадров) также передаются без изменений.
Кэш
Как это работает
Кэш хранит преобразованные изображения в иерархии директорий, индексированных по хешу MD5.
Входные данные для хеширования: URI + source_mtime + target_format + quality, так что:
- Разные форматы (WebP, AVIF) получают отдельные записи в кэше
- Изменение настроек качества приводит к созданию новых записей в кэше
- Изменение оригинального изображения (изменение его mtime) автоматически делает кэшированное преобразование недействительным
Структура директорий
С levels=1:2, ключ кэша a3b1c4d5... создает:
/var/cache/nginx/immerse/a/3b/a3b1c4d5e6f7890123456789abcdef01.webp
Атомарные записи
Файлы кэша записываются атомарно: данные сначала записываются во временный файл, затем
rename() перемещает его на место. Это предотвращает обслуживание частично записанных файлов
под конкурирующей нагрузкой.
Прогрев кэша
В режиме lazy, первый запрос к изображению обслуживает оригинал.
Преобразование выполняется в фоновом режиме, и последующие запросы получают кэшированный
современный формат. В режиме sync, самый первый запрос вызывает преобразование и
обслуживает результат.
Ручная очистка кэша
Чтобы очистить весь кэш:
rm -rf /var/cache/nginx/immerse/*
Перезагрузка NGINX не требуется. Модуль создаст директории по мере необходимости.
Обработка ошибок
ngx_immerse придерживается строгой политики "никогда не ломать то, что уже работает":
| Условие | Поведение |
|---|---|
| Ошибка преобразования (ошибка кодека) | Отдать оригинал, записать ошибку |
| Ошибка записи в кэш (диск заполнен, права доступа) | Отдать преобразованный из памяти, записать предупреждение |
| Поврежденное или усеченное входное изображение | Отдать оригинал, записать ошибку |
Изображение ниже min_size или выше max_size |
Пропустить без изменений |
Нет современного формата в заголовке Accept клиента |
Пропустить без изменений, добавить Vary: Accept |
| Пул потоков не найден | Вернуться к синхронному (блокирующему) преобразованию |
immerse_cache_path не настроен |
Пропустить без изменений, записать ошибку |
| Неизвестный формат изображения (не JPEG/PNG/GIF) | Пропустить без изменений |
Модуль никогда не вернет ошибку 500 из-за сбоя преобразования.
Архитектура
Исходные файлы
| Файл | Назначение |
|---|---|
config |
Интеграция с системой сборки NGINX, обнаружение библиотек |
src/ngx_http_immerse_common.h |
Общие типы, константы, объявления функций |
src/ngx_http_immerse_module.c |
Точка входа модуля, директивы, жизненный цикл конфигурации |
src/ngx_http_immerse_filter.c |
Цепочка фильтров заголовка и тела, конечный автомат, распределение потокового пула |
src/ngx_http_immerse_convert.c |
Движок декодирования/кодирования изображения (работает в потоковом пуле) |
src/ngx_http_immerse_cache.c |
Генерация ключей кэша, поиск, атомарное хранилище |
src/ngx_http_immerse_accept.c |
Парсер заголовка Accept RFC 7231 |
src/ngx_http_immerse_util.c |
Отправка ответа, буферизация тела, обнаружение формата, вспомогательные функции для заголовков |
Безопасность потоков
Вся работа по преобразованию изображений выполняется в рабочих потоках пула NGINX. Код преобразования (ngx_http_immerse_convert.c) использует только malloc/free, POSIX файловый ввод/вывод и вызовы библиотеки изображений. Он никогда не обращается к общей памяти NGINX, пулам запросов или циклу событий.
Результаты возвращаются в основной цикл событий через стандартный механизм завершения задач потоков NGINX (ngx_thread_task_t).
Конечный автомат
Фильтр тела использует конечный автомат на основе фаз:
START -> READ -> CONVERT -> SEND -> DONE (синхронный режим)
PASS -> DONE (ленивый режим, первый запрос)
SERVE_CACHE -> DONE (попадание в кэш)
Тестирование
На основе Docker (рекомендуется)
## Запустите все тесты (Режим HUP для ~10x более быстрой итерации)
make tests
## Запуск конкретного тестового файла
make tests T=t/sync.t
## Запуск без режима HUP (чище состояние между тестами)
make tests HUP=0
## Интерактивная оболочка для отладки
make shell
## Пересобрать базовый образ (после изменений в Dockerfile)
make base-image
## Тестирование на другой версии NGINX
make tests NGINX_VERSION=release-1.26.2
CI
GitHub Actions выполняет тесты на NGINX 1.26.2, 1.27.3 и 1.28.0 при каждом push и pull request.
Набор тестов
| Файл | Покрытие |
|---|---|
t/accept.t |
Парсинг заголовка Accept, q-значения, выбор формата |
t/sync.t |
Преобразование режима синхронизации для JPEG, PNG, GIF в WebP/AVIF |
t/lazy.t |
Ленивая загрузка: сначала оригинал, кэш заполняется позже |
t/cache.t |
Поведение при попадании/промахе в кэше |
t/limits.t |
Фильтрация min_size и max_size |
t/fallback.t |
Резервное копирование поврежденного изображения |
t/passthrough.t |
Модуль отключен, не изображенное содержимое, без заголовка Accept |
t/config.t |
Валидация директивы, переключатель x_header |
t/vary.t |
Наличие заголовка Vary: Accept |
Отладка
- Проверьте
test-error.logв корне репозитория для вывода отладки NGINX - Используйте
make shell, чтобы войти в контейнер и запустить тесты вручную - Уровень логов установлен на
debugв тестовой среде Docker