Почему переменные не работают в директиве try_files в Nginx?

Я пытаюсь решить следующую задачу: вместо раздачи изображений в форматах png и jpeg необходимо предоставлять заранее созданные версии в форматах avif и webp, если браузер их поддерживает. Например, когда браузер запрашивает изображение `image.png`, если он поддерживает и avif, и webp, то сначала следует попытаться отдать `image.png.avif`. Если это изображение отсутствует, то нужно попробовать отдать `image.png.webp`. В случае отсутствия обеих альтернатив следует вернуть оригинал `image.png`. <br/> <br/> Основная логика такова: <br/> <br/> <pre><code>location ~* \.(png|jpe?g|gif)$  {
  set $avif_image_path "";
  if ($http_accept ~* "image/avif") {
    set $avif_image_path "$uri.avif";
  }

  set $webp_image_path "";
  if ($http_accept ~* "image/webp") {
    set $webp_image_path "$uri.webp";
  }

  add_header Vary Accept;

  try_files $avif_image_path $webp_image_path $uri =404;
}</code></pre> <br/> <br/> Однако Nginx всегда возвращает оригинальный файл изображения. <br/> <br/> Я предоставляю полный листинг конфигурации и ссылку на репозиторий с примером: <a href="https://github.com/monochromer/nginx-image-test" rel="nofollow">https://github.com/monochromer/nginx-image-test</a>
  • 8 декабря 2024 г. 11:03
Ответы на вопрос 2
Чтобы решить вашу задачу с использованием директивы `try_files` и переменных в Nginx, необходимо учесть несколько особенностей работы Nginx с переменными. Ваша проблема связана с тем, что переменные, которые устанавливаются внутри директивы `if`, не могут быть использованы в контексте `try_files` в Nginx.

Директива `try_files` обрабатывает свои аргументы до того, как будут оценены переменные, что приводит к тому, что значения переменных, назначенные внутри `if`, не будут доступны.

Для корректной работы с вашими условиями я рекомендую эмулировать поведение в одном из следующих подходов:

### Подход 1: Использование `map`

Можно использовать директиву `map`, чтобы определить, какой именно файл пытаться отдавать, в зависимости от заголовка `Accept`:

```nginx
map $http_accept $image_format {
    ~*image/avif  $uri.avif;
    ~*image/webp  $uri.webp;
    default       $uri;
}

location ~* \.(png|jpe?g|gif)$ {
    add_header Vary Accept;
    try_files $image_format $uri =404;
}
```

### Подход 2: сократить использование директивы `if`

Также можно заменить использование директивы `if` на более прямолинейный подход с использованием `try_files` в виде:

```nginx
location ~* \.(png|jpe?g|gif)$ {
    add_header Vary Accept;

    # Первой проверяем поддержку AVIF
    try_files $uri.avif $uri.webp $uri =404;
}
```

Но данный метод потребует правильного генерации файлов `image.png.avif`, `image.png.webp` заранее.

### Примечания

1. Убедитесь, что у вас есть соответствующие версии изображений (например, `image.png.avif` и `image.png.webp`) в директории, где хранятся оригинальные изображения.
2. Проверьте, чтобы ваши конфигурационные файлы были прогружены без ошибок, после того как вы внесли изменения.
3. Используйте `nginx -t`, чтобы проверить конфигурацию перед перезапуском сервера.

Таким образом, предотвращая использование переменных в контексте `if`, вы сможете эффективно настроить Nginx для раздачи изображений в нужных форматах в зависимости от поддержки браузера.
Когда истинно уловие if исполняется только содержимое блока if. 
И то что унаследовано от основной конфигурации, например, add_header .
А вот try_files не наследуется и исполняется только когда уловие if ложно.

Эти проблемы возникают только в контексте location .
При использовании if в контексте server таких проблем нет.
Достаточно перенести if на уровень server и всё заработает как ожидалось.
server {
  set $avif_image_path "";
  if ($http_accept ~* "image/avif") {
    set $avif_image_path "$uri.avif";
  }

  set $webp_image_path "";
  if ($http_accept ~* "image/webp") {
    set $webp_image_path "$uri.webp";
  }

  location ~* \.(png|jpe?g|gif)$  {
    add_header Vary Accept;

    try_files $avif_image_path $webp_image_path $uri =404;
  }


Или как советовали в комментариях избавиться от if и переписать на map .
Похожие вопросы