1. 1
  2. 2
  3. 3
  4. 4
  5. 5

# теория · шаг 1 из 5

Вложенные location

Коротко

location можно вложить в location, но правила тут строже, чем кажется, и часть ошибок nginx ловит сам.

  • Вложенный блок обязан лежать ВНУТРИ внешнего префикса. Иначе конфиг не загрузится: location "/cart" is outside location "/api/".
  • Внутрь регулярки можно вложить только регулярку, внутрь именованного блокаРазбирается в разделе 4, глава «Вложенные и именованные location»: Именованные location (location @имя) - ничего.
  • Вложенные блоки сравниваются с полным URI, а не с остатком пути.
  • Поиск идёт по тем же пяти шагам, и вложенные регулярки проверяются раньше внешних.
  • Вкладывают ради наследования - и помнят, что add_header при этом не дополняется, а замещается.

Если вложенность используешь осознанно - листай до «Теперь сам».

Сначала ответь сам

Хочется описать корзину внутри блока API:

/etc/nginx/conf.d/shop.confhttp
server {
    listen 80;
    location /api/ {
        proxy_pass http://127.0.0.1:8080;

        location /cart {
            add_header X-Cart "yes" always;
        }
    }
}

Что скажет nginx -t?

Инстинктивный ответ - «всё в порядке, просто блок не сработает: он же про /cart, а не про /api/cart». Настоящий - конфиг не загрузится вовсе.

На что это похоже

Вложенные блоки в nginx - это не вложенные папки, а уточнения на одном и том же адресе.

Представь распоряжение по этажу и распоряжение по кабинету. Второе имеет смысл, только если кабинет действительно на этом этаже. Написать в распоряжении по третьему этажу пункт про кабинет на пятом нельзя - это не «бесполезный пункт», это ошибка в документе, и её вернут на доработку.

Nginx поступает так же и возвращает на доработку сам: проверяет при загрузке, лежит ли вложенный путь внутри внешнего.

Механизм: дерево, которое проверяется при загрузке

↳  выводnginx -t
nginx: [emerg] location "/cart" is outside location "/api/" in /etc/nginx/conf.d/shop.conf:6
nginx: configuration file /etc/nginx/nginx.conf test failed

[emerg] - метка серьёзности, «авария»: с таким конфигом nginx не запустится. Сообщение точное и означает буквально то, что написано. Правило одно: путь вложенного блока должен начинаться с пути внешнего. Правильно так:

/etc/nginx/conf.d/shop.confhttp server
location /api/ {
    proxy_pass http://127.0.0.1:8080;

    location /api/cart {
        proxy_pass http://127.0.0.1:8080;
        add_header X-Cart "yes" always;
    }
}

proxy_pass пришлось повторить: обработчик содержимого во вложенный блок не наследуется, это было в разделе про язык конфига. Проверено: /api/cart уходит на бэкенд и несёт X-Cart: yes.

Отсюда следуют ограничения, которые тоже проверяются при загрузке.

внешний блок что можно вложить location /api/ префикс, точное и регулярку - если путь начинается с /api/ location ~ ^/api/ только другую регулярку: у шаблона нет пути, внутри которого лежать location @backend ничего: это конечная точка Нарушение любого правила - ошибка при загрузке.
Три вида внешнего блока и что в них разрешено. Это ошибка при загрузке, а не тихая неработающая строка, поэтому до прода такие опечатки не доживают.

Правило

Вложенный блок обязан лежать внутри внешнего префикса, и сравнивается он с полным URI, а не с остатком пути. Внутрь регулярки вкладывается только регулярка, внутрь именованного блока - ничего.

Разбор: как идёт поиск внутри

Внутри победившего префикса поиск повторяет те же пять шагов. Проверено на nginx 1.31.5.

/etc/nginx/conf.d/shop.confhttp server
location /api/ {
    return 200 "внешний /api/\n";
    location ~ \.json$   { return 200 "вложенная регулярка\n"; }
    location /api/cart   { return 200 "вложенный префикс\n"; }
    location = /api/ping { return 200 "вложенное точное\n"; }
}
$  команда
for u in /api/x /api/report.json /api/cart /api/ping; do printf '%-22s -> ' $u; curl -s localhost$u; done
↳  выводfor u in /api/x /api/report.json /api/cart /api/ping; do printf '%-22s -> ' $u; curl -s localhost$u; done
/api/x                 -> внешний /api/
/api/report.json       -> вложенная регулярка
/api/cart              -> вложенный префикс
/api/ping              -> вложенное точное

return стоит во внешнем блоке первой строкой, и всё же /api/cart получил не «внешний /api/»: сначала выбирается location, и только потом выполняется то, что в нём написано. Внешний return достаётся лишь адресам, которым не нашлось вложенного блока.

Всё как этажом выше: точное совпадение сильнее, префикс запоминается, регулярка бьёт префикс. Разница ровно одна - кандидатов ищут только среди вложенных. Вложенную регулярку тоже сравнивают с полным URI, но проверять в ней начало пути не обязательно: сюда и так попадают только адреса внутри /api/.

Разбор: где наивное правило подводит

Наивное правило: «регулярки на уровне server проверяются всегда».

Проверяются, но позже вложенных. Два блока в одном конфиге:

/etc/nginx/conf.d/shop.confhttp server
location /api/ {
    return 200 "внешний префикс /api/\n";
    location ~ \.xml$ { return 200 "вложенная регулярка xml\n"; }
}
location ~ \.json$ { return 200 "ВНЕШНЯЯ регулярка json\n"; }
$  команда
for u in /api/report.xml /api/report.json; do printf '%-22s -> ' $u; curl -s localhost$u; done
↳  выводfor u in /api/report.xml /api/report.json; do printf '%-22s -> ' $u; curl -s localhost$u; done
/api/report.xml        -> вложенная регулярка xml
/api/report.json       -> ВНЕШНЯЯ регулярка json

То есть очередь такая: сначала регулярки того уровня, где остановился поиск по префиксам, и только если там никто не совпал - регулярки уровнем выше.

Практическое следствие неприятное. Добавив во вложенный блок одну регулярку, ты меняешь маршрут не только для неё: для всех адресов внутри этого префикса теперь первым спрашивают её, а не общий список сайта. На двух уровнях это ещё читается. На трёх ответ на вопрос «какой блок обработает этот URI» перестаёт быть очевидным при взгляде на конфиг - и именно поэтому глубокую вложенность не любят.

Что ломается без этого

  • location "X" is outside location "Y" при загрузке. Вложенный путь не начинается с внешнего. Допиши внешний префикс к вложенному.
  • Конфиг не принимает вложение в регулярку. Внутрь ~ можно только ~. Если нужен префикс - выноси блок наружу.
  • Общая регулярка сайта перестала работать на части адресов. Внутри префикса появилась своя, и она спрашивается раньше.
  • Заголовки исчезли во вложенном блоке. Это не про вложенность, а про add_header: список замещается целиком. Лечится add_header_inherit merge.

Зачем это в работе

Вложенность берут ради одной вещи - не повторять общие настройки:

/etc/nginx/conf.d/shop.confhttp server
location /uploads/ {
    root /var/www;
    autoindex off;

    location ~ ^/uploads/.+\.(jpg|png)$ { expires 30d; }
}

autoindex off запрещает показывать список файлов каталога, а expires 30d велит браузеру хранить картинку тридцать дней (подробнее о кешированииРазбирается в разделе 5, глава «Кеш-заголовки и отдача кусками»: expires, immutable и always). root и autoindex написаны один раз, а срок кеша добавлен только картинкам. Плоский вариант потребовал бы повторить root в обоих блоках - и однажды поправить только в одном.

Граница простая: два уровня читаются, три - нет. Если начинает рябить, разложи маршруты плоским списком (location /api/v1/, location /api/v2/). Конфиг станет длиннее, зато на вопрос «какой блок обработает этот URI» отвечают взглядом, а не разбором дерева.

Теперь сам

1. Почему location /api/ { location ~ \.json$ { } } загружается, а location /api/ { location /cart { } } - нет?

У регулярки нет пути, внутри которого она должна лежать: nginx проверяет вложенность только у префиксных и точных блоков. У /cart путь есть, и он не начинается с /api/.

2. Внутри location /static/ добавили location ~ \.css$ с заголовком кеша. На сайте есть общий блок location ~* \.(css|js)$ с другими настройками. Что теперь получит /static/app.css?

Вложенный блок: регулярки того уровня, где остановился поиск по префиксам, спрашиваются раньше. Общий блок для этого файла больше не работает, и это именно то, о чём легко забыть через полгода.

3. Как переписать конфиг из первого вопроса урока, чтобы он загрузился и делал задуманное?

/etc/nginx/conf.d/shop.confhttp server
location /api/ {
    proxy_pass http://127.0.0.1:8080;

    location /api/cart {
        proxy_pass http://127.0.0.1:8080;
        add_header X-Cart "yes" always;
    }
}

Путь вложенного блока начинается с внешнего - конфиг загрузится. А proxy_pass повторён внутри, потому что вложенным блоком он не наследуется: без него корзина перестала бы уходить на бэкенд.

Главное

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

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.