# теория · шаг 2 из 5
Именованные location
Коротко
location @имя - подпрограмма маршрутизации. Сам по себе не выбирается
никогда, попасть в него можно только явной ссылкой.
- Ссылаются двумя директивами:
try_filesпоследним аргументом иerror_pageчерез=. - Классика:
try_files $uri $uri/ @backend- статика с диска, всё остальное в приложение. - Смысл имени - написать блок проксирования один раз и ссылаться из нескольких мест.
error_pageне перехватывает ответ бэкенда безproxy_intercept_errors on. Он ловит только то, что породил сам nginx, - и это ломает самый популярный рецепт страницы обслуживания.- Вложить внутрь
@ничего нельзя: это конечная точка маршрута.
Если про proxy_intercept_errors уже знаешь - листай до «Теперь сам».
Сначала ответь сам
Хотим показывать свою страницу вместо ошибки бэкенда:
location /api/ {
proxy_pass http://127.0.0.1:8080;
error_page 502 504 = @maintenance;
}
location @maintenance { return 503 "идут работы"; }
Приложение живо, но на этот запрос само отвечает 502 - например, у него отвалилась база. Что увидит клиент?
Инстинктивный ответ - «идут работы», ради этого конфиг и написан. Настоящий - 502 и страницу от приложения. Именованный блок не сработает.
На что это похоже
Именованный блок - это внутренний номер, а не дверь с улицы.
В здание заходят через вход и кабинеты с табличками - это обычные location.
А есть номер, по которому нельзя дозвониться снаружи: на него можно только
перевести звонок изнутри. Никакой посетитель не попадёт туда сам, сколько бы
он ни искал нужную дверь.
Отсюда и смысл: в такой «кабинет» кладут то, что нужно нескольким отделам сразу, - чтобы не держать три одинаковых инструкции, которые однажды разойдутся.
Механизм: две двери внутрь
location / {
try_files $uri $uri/ @backend;
}
location @backend {
proxy_pass http://127.0.0.1:8080;
}
Читается конфиг так: «есть такой файл - отдай его; есть такой каталог - отдай
индекс из него (файл, который отдают вместо каталога, по умолчанию
index.html); ничего нет - иди в @backend». Вторая дверь, error_page,
устроена так же: location /static/ { error_page 404 = @backend; } отдаёт
файл с диска, а за несуществующим идёт в приложение - проверено. Это конфиг, который нужен
большинству приложений.
Директиву в try_files не вставить - последним аргументом он принимает либо
URI, либо код вида =404 («ответить кодом 404»; знак тот же, что у точного
location, смысл свой), либо именованный блок. Имя и существует затем,
чтобы блок с настройками лежал в одном месте:
location / { try_files $uri @backend; }
location /admin/ { try_files $uri @backend; }
location @backend {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
Две строки proxy_set_header сообщают приложению имя сайта и адрес клиента
(зачем это нужноРазбирается в разделе 7, глава «Заголовки: Host, X-Real-IP, X-Forwarded-*»: Что бэкенд перестаёт видеть). Настройки
проксирования написаны один раз. Без имени их пришлось бы
продублировать - и однажды поправить только в одном месте.
Правило
В именованный блок нельзя попасть по URI: только из try_files или
error_page. Держат в нём то, что нужно из нескольких мест.
Разбор: как это работает живьём
Проверено на nginx 1.31.5. Первый server-блок - подставной бэкенд, как в
разделе про язык конфига; на диске, в /srv/site, лежит только real.txt:
server {
listen 8080;
return 200 "БЭКЕНД $request_uri\n";
}
server {
listen 80;
root /srv/site;
location / { try_files $uri $uri/ @backend; }
location @backend { proxy_pass http://127.0.0.1:8080; }
}
for u in /real.txt /net-takogo /@backend; do printf '%-14s -> ' $u; curl -s localhost$u; done
/real.txt -> файл с диска
/net-takogo -> БЭКЕНД /net-takogo
/@backend -> БЭКЕНД /@backend
Первый запрос закрыт файлом, второй ушёл в приложение. Третий - проверка на то,
что имя действительно не адрес: /@backend обработался как обычный путь и ушёл
в приложение вместе со всеми остальными.
Разбор: где наивное правило подводит
Наивное правило: «error_page 502 = @maintenance показывает мою страницу, когда
бэкенд отвечает 502».
Разница в том, кто породил ошибку. Три случая, все проверены. Второй
подставной бэкенд на порту 8082 на всё отвечает ошибкой -
server { listen 8082; return 502 "502 ОТ БЭКЕНДА"; }, а на порту 9999 не
слушает никто:
location /otvechaet/ { # бэкенд сам отвечает 502
proxy_pass http://127.0.0.1:8082;
error_page 502 504 = @maintenance;
}
location /perehvat/ { # то же плюс перехват
proxy_pass http://127.0.0.1:8082;
proxy_intercept_errors on;
error_page 502 504 = @maintenance;
}
location /net-bekenda/ { # бэкенда нет вовсе
proxy_pass http://127.0.0.1:9999;
error_page 502 504 = @maintenance;
}
for u in /otvechaet/x /perehvat/x /net-bekenda/x; do echo -n "$u -> "; curl -s -w " код=%{http_code}\n" localhost$u; done
/otvechaet/x -> 502 ОТ БЭКЕНДА код=502
/perehvat/x -> идут работы код=503
/net-bekenda/x -> идут работы код=503
-w велит curl напечатать после тела код ответа. Знак = в
error_page 502 504 = @maintenance значит «код возьми из того, куда
переводишь»: @maintenance отвечает 503, его клиент и получает. 502 - бэкенд
ответил ошибкой или мусором, 504 - его не дождались, 503 - «сервис временно
недоступен», честный код для работ.
Первая строка и есть ловушка. error_page перехватывает ошибки, которые
породил сам nginx: не смог соединиться, не дождался ответа, не нашёл файл.
Ответ, пришедший от приложения, он по умолчанию пропускает как есть - вместе с
его телом и его кодом.
Включает перехват proxy_intercept_errors on. Перехватываются только коды,
перечисленные в error_page: замер с тем же error_page 502 504 показал, что
осмысленный 404 приложения доезжает до клиента нетронутым. Опасность в том,
что список легко расширить - error_page 404 403 500 502 504 = @maintenance, -
и тогда твоя страница съест 404 и 403 с телом, которое приложение специально
сформировало для клиента. Поэтому перехватывают точечно - в том location, где
приложение заведомо не умеет отвечать по-человечески.
Что ломается без этого
- Страница обслуживания не показывается. Ошибку вернуло приложение, а не
nginx. Нужен
proxy_intercept_errors on. - После включения перехвата пропали осмысленные 404 приложения. Обратная
сторона: перехватывается всё, что перечислено в
error_page. Сузь список кодов или сам location. location "..." cannot be inside the named location "@...". Внутрь именованного блока вложить нельзя ничего.- Запрос неожиданно уходит в приложение. В
try_filesпоследним аргументом стоит имя - значит, туда попадает всё, что не нашлось на диске, включая опечатки в адресах.
Зачем это в работе
Именованные блоки берут в двух ситуациях, и обе повторяются на каждом проекте.
Первая - приложение с фронтендом: статика уходит с диска, остальное в код.
Одна строка try_files $uri $uri/ @backend заменяет любые попытки перечислить
маршруты приложения в конфиге - и не устаревает, когда во фронтенде появляется
новый адрес.
Вторая - страница обслуживания, которая показывается вместо ошибки. Тут важно проверять её не рассуждением, а опытом: погасить бэкенд и посмотреть, что приехало. Ровно на этой проверке и вскрывается разница между «бэкенд лежит» и «бэкенд ответил ошибкой» - два случая, которые выглядят одинаково в мониторинге и по-разному в конфиге.
Теперь сам
1. Почему try_files $uri $uri/ @backend; лучше, чем
try_files $uri $uri/ /index.html; для приложения с сервером на бэкенде?
Второй вариант отдаёт файл index.html с диска - это годится для фронтенда,
который целиком собран в статику. Если за адресами стоит серверный код, ему
надо передать сам запрос, а сделать это может только блок с proxy_pass.
2. Что произойдёт с запросом /@maintenance, если в конфиге есть
location @maintenance?
Обработается как обычный путь: @maintenance в адресе - просто набор символов.
Пойдёт в тот блок, который выиграет обычный выбор, - скорее всего, в
location /.
3. Приложение отдаёт 404 со своей красивой страницей, а nginx на несуществующий файл отдаёт голый 404. Как сделать одинаково, не сломав первое?
Написать error_page 404 /404.html; в том location, который отдаёт файлы с
диска, и не включать proxy_intercept_errors в блоке проксирования. Тогда
свою страницу показывает nginx там, где ошибку породил он, а ответы приложения
доезжают до клиента нетронутыми.
Главное
location @имя - подпрограмма: сам по себе не выбирается никогда, попасть
внутрь можно только из try_files или error_page. Держат в нём то, что нужно
из нескольких мест. И помни: error_page ловит ошибки самого nginx, а ответ
бэкенда перехватывается только с proxy_intercept_errors on.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий