# теория · шаг 1 из 5
Пять форм записи location
Коротко
Server-блок выбран, дальше nginx решает второй вопрос: какой location внутри
него обработает путь. Форм записи пять, и они не равны между собой.
=- точное совпадение всего URI,^~- префикс, отменяющий регулярки,~и~*- регулярки, без модификатора - обычный префикс.- Префикс сравнивается как строка, а не как путь.
location /appловит и/application.log, и/appliance. - Строка запроса в выборе не участвует:
?sort=priceотрезается заранее. - URI приводится к нормальному виду ДО выбора:
..схлопывается,%2eи двойные слеши тоже. Сравнивают уже результат, а не то, что прислал клиент. - Порядок строк в файле решает только споры между регулярками.
Если пять форм и «префикс - это строка» уже очевидны - листай до «Теперь сам».
Сначала ответь сам
В конфиге один блок:
location /app { proxy_pass http://127.0.0.1:8080; }
Три запроса: /app/index.html, /application.log и /appliance. Сколько из
них уйдёт на бэкенд?
Инстинктивный ответ - один: остальные два к приложению отношения не имеют. Настоящий - все три.
На что это похоже
Представь сортировку заявок по шаблонам на конверте.
Один сотрудник ищет конверт, надписанный ровно так, как у него в образце, - буква в букву. Второму сказано брать всё, что начинается со слова «ремонт», - и он честно берёт и «ремонт кровли», и «ремонтопригодность», потому что сверяет начало строки, а не смысл. Третий читает надпись по шаблону с подстановками и разбирается сложнее, но и работает дольше.
И есть конверт с пометкой «сюда, и дальше не разбирать»: он забирает заявку у всех остальных, включая того, кто читает по шаблону.
Nginx устроен ровно так, и главная ошибка новичка - думать, что второй сотрудник понимает, где кончается слово.
Механизм: пять форм и что каждая означает
location = /favicon.ico { } # 1. точное совпадение всего URI
location ^~ /images/ { } # 2. префикс, отменяющий регулярки
location ~ \.php$ { } # 3. регулярка, с учётом регистра
location ~* \.(jpg|png)$ { } # 4. регулярка, без учёта регистра
location /api/ { } # 5. обычный префикс
В регулярках \. - буквальная точка (без слэша точка значит «любой знак»), $ -
конец строки, (jpg|png) - одно из перечисленного. Пробел между модификатором
и путём ставят по традиции: location =/favicon.ico и location ~*\.jpg$
nginx тоже понимает, проверено. Пустые { } допустимы - такой блок отдаёт
файлы с диска по общим правилам сервера.
= требует совпадения всего URI. location = / поймает / и /?a=1 -
строку запроса отрезают до выбора, - но не /index.html: это уже другой путь.
^~ совпадает как обычный префикс, но если победил - регулярки не
проверяются вовсе. Значок читается не «регулярка», а «префикс, и на этом
хватит».
~ и ~* различаются только чувствительностью к регистру. Совпадение
ищется в любом месте URI, поэтому ~ \.php без $ поймает и
/upload.php.jpg.
Без модификатора - совпадение по началу строки.
Правило
Префикс сравнивается как строка, а не как путь. Имеешь в виду каталог - пиши
слеш: location /app/. Без слеша блок заберёт всё, что начинается с этих
букв.
Разбор: что ловит каждая форма
Собрал конфиг из всех пяти форм и прогнал по нему запросы. Проверено на nginx 1.31.5.
location = /favicon.ico { return 200 "точное\n"; }
location ^~ /assets/ { return 200 "^~ префикс\n"; }
location ~ \.png$ { return 200 "регулярка png\n"; }
location ~* \.JPG$ { return 200 "регулярка без регистра\n"; }
location /images/ { return 200 "префикс images\n"; }
location /app { return 200 "префикс app\n"; }
location / { return 200 "префикс корня\n"; }
for u in /favicon.ico /assets/logo.png /images/logo.png /images/note.txt /photo.JPG /application.log /appliance /nothing; do printf '%-22s -> ' $u; curl -s localhost$u; done
/favicon.ico -> точное
/assets/logo.png -> ^~ префикс
/images/logo.png -> регулярка png
/images/note.txt -> префикс images
/photo.JPG -> регулярка без регистра
/application.log -> префикс app
/appliance -> префикс app
/nothing -> префикс корня
Цикл for подставляет адреса по очереди в $u и для каждого выполняет всё
между do и done; printf '%-22s -> ' печатает адрес, дополненный пробелами
до ровной колонки. В терминал команда вставляется одной строкой.
Три строки стоит разобрать. /assets/logo.png ушёл в префикс, хотя регулярка
\.png$ подходит: ^~ отменил её. /images/logo.png с той же регуляркой ушёл
в неё, потому что у /images/ этой защиты нет - об этом весь следующий урок.
А /application.log и /appliance попали в блок приложения, и это ответ на
вопрос из начала урока.
Разбор: где наивное правило подводит
Наивное правило: «в location сравнивается то, что клиент написал в адресе».
Сравнивается нормализованный URI - после декодирования процентов,
схлопывания .. и склейки повторных слешей. %2e - закодированная точка: в
адресе любой знак можно записать как % и его код. Одна тонкость опыта: curl
сам схлопывает .. ещё до отправки, поэтому нужен флаг --path-as-is - он
шлёт путь как написано. Тот же конфиг, ещё три запроса:
for u in '/images/../secret/' '/images/%2e%2e/secret/' '//images//logo.txt'; do printf '%-22s -> ' $u; curl -s --path-as-is localhost$u; done
/images/../secret/ -> префикс корня
/images/%2e%2e/secret/ -> префикс корня
//images//logo.txt -> префикс images
А /images/logo.png?x=1 ушёл в регулярка png: строка запроса отрезана до
выбора - иначе ?x=1 ломал бы привязку $ в любой регулярке. Путь /images/../secret/ превратился в /secret/ и в блок картинок
не попал вовсе, а закодированный %2e%2e сначала раскодировался и дал то же
самое.
Это важнее, чем выглядит. Правило доступа, написанное как
location /internal/ { deny all; }, не обойти ни точками, ни процентами, ни
двойными слешами: к моменту выбора все три записи уже приведены к одному виду.
deny all запрещает доступ всем, и клиент получает 403 - замер дал его и на
/images/../internal/x с --path-as-is.
И наоборот - location не может отличить /images//logo.png от
/images/logo.png, даже если тебе это зачем-то понадобилось.
Что ломается без этого
- В блок приложения попадают чужие адреса. Забыт слеш:
location /appвместоlocation /app/. location = /не ловит главную страницу с параметрами. Ловит: строка запроса в сравнении не участвует. Если не ловит - ищи другую причину.- Регулярка
~ \.phpсрабатывает на/photo.php.jpg. Нет привязки к концу: нужен$. - Правило
denyне срабатывает, хотя адрес похож. Сравни его с нормализованным URI, а не с тем, что в браузере:nginx -Tплюс запрос курлом отвечают за минуту.
Зачем это в работе
Пропущенный слеш в префиксе - самая дешёвая по написанию и самая дорогая по
последствиям опечатка в конфиге. Она не ломает ничего сразу: блок работает,
сайт открывается, nginx -t доволен. Ломается позже, когда рядом появляется
адрес с теми же первыми буквами.
Отсюда привычка, которая экономит вечера: писать префикс со слешем всегда, когда имеешь в виду каталог, и проверять новый блок не одним «правильным» адресом, а тремя - похожим, коротким и лишним:
for u in /app/ /app /application.log; do echo -n "$u -> "; curl -s localhost$u; done
echo -n печатает адрес без перевода строки, чтобы ответ встал рядом. На
удалённом сервере вместо localhost пишут его имя.
Три секунды, и видно, что блок забирает больше, чем ты думал.
Теперь сам
1. Чем location /api отличается от location /api/ для запроса /api?
Первый его поймает, второй - нет: /api не начинается с /api/. Отсюда
типовой симптом «работает со слешем на конце и не работает без него». Если
нужны оба, их либо перечисляют двумя блоками, либо ставят редирект с одного на
другой: location = /api { return 301 /api/; } отвечает 301 с
Location: http://localhost/api/, проверено.
2. Почему location ~ \.php$ не поймает /upload.PHP?
~ учитывает регистр. Поймает ~* \.php$. Регистр в путях - вечный источник
расхождений между разработкой на одной системе и продом на другой.
3. Есть location ^~ /assets/ и location ~ \.png$. Куда уйдёт
/assets/logo.png и куда /avatars/logo.png?
Первый - в ^~ /assets/: этот модификатор отменяет проверку регулярок.
Второй - в регулярку: под префикс /assets/ он не подошёл, значит защищать
нечего.
Главное
Форм пять: =, ^~, ~, ~* и обычный префикс. Префикс сравнивается как
строка, а не как путь, поэтому /app ловит и /application.log. Сравнивается
нормализованный URI - без строки запроса, с раскодированными процентами и
схлопнутыми ...
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий