# теория · шаг 1 из 5
Вложенные location
Коротко
location можно вложить в location, но правила тут строже, чем кажется, и
часть ошибок nginx ловит сам.
- Вложенный блок обязан лежать ВНУТРИ внешнего префикса. Иначе конфиг не
загрузится:
location "/cart" is outside location "/api/". - Внутрь регулярки можно вложить только регулярку, внутрь
именованного блокаРазбирается в разделе 4, глава «Вложенные и именованные location»: Именованные location (
location @имя) - ничего. - Вложенные блоки сравниваются с полным URI, а не с остатком пути.
- Поиск идёт по тем же пяти шагам, и вложенные регулярки проверяются раньше внешних.
- Вкладывают ради наследования - и помнят, что
add_headerпри этом не дополняется, а замещается.
Если вложенность используешь осознанно - листай до «Теперь сам».
Сначала ответь сам
Хочется описать корзину внутри блока API:
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: [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 не запустится.
Сообщение точное и означает буквально то, что написано. Правило одно: путь
вложенного блока должен начинаться с пути внешнего. Правильно так:
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.
Отсюда следуют ограничения, которые тоже проверяются при загрузке.
Правило
Вложенный блок обязан лежать внутри внешнего префикса, и сравнивается он с полным URI, а не с остатком пути. Внутрь регулярки вкладывается только регулярка, внутрь именованного блока - ничего.
Разбор: как идёт поиск внутри
Внутри победившего префикса поиск повторяет те же пять шагов. Проверено на nginx 1.31.5.
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
/api/x -> внешний /api/
/api/report.json -> вложенная регулярка
/api/cart -> вложенный префикс
/api/ping -> вложенное точное
return стоит во внешнем блоке первой строкой, и всё же /api/cart получил не
«внешний /api/»: сначала выбирается location, и только потом выполняется то, что
в нём написано. Внешний return достаётся лишь адресам, которым не нашлось
вложенного блока.
Всё как этажом выше: точное совпадение сильнее, префикс запоминается,
регулярка бьёт префикс. Разница ровно одна - кандидатов ищут только среди
вложенных. Вложенную регулярку тоже сравнивают с полным URI, но проверять в ней
начало пути не обязательно: сюда и так попадают только адреса внутри /api/.
Разбор: где наивное правило подводит
Наивное правило: «регулярки на уровне 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
/api/report.xml -> вложенная регулярка xml
/api/report.json -> ВНЕШНЯЯ регулярка json
То есть очередь такая: сначала регулярки того уровня, где остановился поиск по префиксам, и только если там никто не совпал - регулярки уровнем выше.
Практическое следствие неприятное. Добавив во вложенный блок одну регулярку, ты меняешь маршрут не только для неё: для всех адресов внутри этого префикса теперь первым спрашивают её, а не общий список сайта. На двух уровнях это ещё читается. На трёх ответ на вопрос «какой блок обработает этот URI» перестаёт быть очевидным при взгляде на конфиг - и именно поэтому глубокую вложенность не любят.
Что ломается без этого
location "X" is outside location "Y"при загрузке. Вложенный путь не начинается с внешнего. Допиши внешний префикс к вложенному.- Конфиг не принимает вложение в регулярку. Внутрь
~можно только~. Если нужен префикс - выноси блок наружу. - Общая регулярка сайта перестала работать на части адресов. Внутри префикса появилась своя, и она спрашивается раньше.
- Заголовки исчезли во вложенном блоке. Это не про вложенность, а про
add_header: список замещается целиком. Лечитсяadd_header_inherit merge.
Зачем это в работе
Вложенность берут ради одной вещи - не повторять общие настройки:
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. Как переписать конфиг из первого вопроса урока, чтобы он загрузился и делал задуманное?
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, а не с остатком пути. Поиск внутри
идёт по тем же пяти шагам, причём вложенные регулярки спрашиваются раньше
внешних.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий