Частный случай с одним уровнем вложенности
Если Вы новичок в nginx, то следует рассмотреть вначале частный случай без использования вложненных location, т.?к. алгоритм для частного случая значительно проще:
- Вначале будет искаться равенство (=). Оно имеет высший приоритет.
- Потом будет искаться максимальный по длине префиксный location
(( ? ) или (^~)) , после чего будет проверено, есть ли на найденном location модификатор приоритета (^~), и если он есть, то будет возвращён этот location. - Потом будут проверяться регулярные выражения
((~) и (~*)) сверху вниз. При совпадении будет возвращён первый location из них. - Потом вернётся тот префиксный location, который мы нашли до этого.
Обратите внимание, что этот алгоритм не применим при наличии вложенных location.
Общий случай с вложенными location
- Стартуем с верхнего уровня.
- Если на текущем уровне выполняется равенство (=), поиск прекращается — это и будет результат,
т.?к. такой location не может иметь никаких других вложенных location. - В противном случае ищем на текущем уровне самый большой префиксный location
(( ? ) или (^~)). - Если такой префиксный location существует, то делаем его текущим уровнем и переходим к п.?2.
- В противном случае выходим из цикла.
- Мы вышли из цикла. На данный момент мы нашли «самый большой» префиксный location, но не думайте, что это самый большой из всех. Пример:
location /abc { location /abcdefghi { … } } location /abcdef { … }
В данном примере мы перейдём в /abcdef,т.?к. на его уровне он переборол более короткий /abc. Но по факту существуют location и больше него. - Теперь в найденном location мы ищем первый верный regexp. При нахождении поиск полностью прекращается. Обратите внимание: в этом пункте мы по факту ищем regexp на самом нижнем уровне, а не на верхнем, как многие могли бы подумать.
Т.?е. поиск regexp идёт снизу, а не сверху (но внутри одного уровня идёт сверху, а не снизу).- Далее, если ничего не найдено, поднимаемся на один уровень вверх и аналогично ищем первый regexp, но в этот раз уже только при условии, что location, в котором мы были до этого, не имел метки (^~). Повторяем этот пункт до тех пор, пока подниматься будет некуда.
- При этом нужно иметь ввиду:
- Даже если какой-то из уровней имеет метку (^~), это не значит, что мы не осуществляем подъём. Подъём осуществляется всегда, но если более нижний уровень имел метку (^~), то на текущем уровне поиск regexp'ов не проводится.
- Возможности запретить проверку regexp в самом нижнем уровне нет — для этого нужно создать ещё один вложенный уровень. А вот запретить проверку regexp на нулевом уровне можно — для этого location первого уровня (который находится на нулевом уровне) должен иметь метку (^~).
- Мы сделали подъём по дереву, но так и не нашли ни одного regexp. Раз regexp не найден, возвращаем «почти самый большой» префиксным location, который был найден ранее. Готово.
Также при этом:
- В версиях 0.7.1–0.8.41 префиксный location ( ? ) при точном совпадении действует как =
Пример уязвимого конфига
location ~ \.php$ {
deny all; # Здесь должно быть проксирование на php-fpm
}
location /posts/ {
location ~ (.*)_2x(\.[a-z]+)$ {
try_files $uri $1$2 =404;
}
}
В данном конфиге мы настроили игнорирование "_2x", если файл не найден. Например, nginx попробует найти файл /posts/img/a_2x.png как по указанному пути, так и по пути /posts/img/a.png. Но в реальности, если мы запросим /posts/authData_2x.php, то мы получим исходный текст скрипта authData.php в голом виде. Чтобы избежать таких ошибок, нужно знать, как обрабатывается location в nginx.
Также дополнительной защитой может являться хранение скриптов в отдельной директории, недоступной из под обычных location. В этом случае, если наш location на php по каким-то причинам не сработает, пользователь получит ошибку 404, а не исходный текст скрипта.
Перенаправление location
- Если try_files не содержит кода ошибки последним параметром, то будет сделано перенаправление в другой location,
т.?к. последний параметр всегда делает перенаправление. Обратите внимание: код ошибки в try_files должен писаться через равно (=). - index и error_page при срабатывании всегда делают перенаправление в другой location. Также перенаправление делает rewrite, если добавить в него флаг last.
Другое
- При выборе location не учитывается строка запроса, которая начинается со знака "?".
Отказ от ответственности
Алгоритм в статье составлен мной на основе моих личных наблюдений, и не факт, что он является правильным. К сожалению, официальной документации нет, а исходники я не читал. Если кто-то найдёт ошибки в алгоритме, просьба написать личным сообщением или в комментариях.
Комментарии (20)
symbix
03.02.2018 15:02К сожалению, официальной документации нельзя доверять
Почему? Что там не так?
vitaliy2 Автор
03.02.2018 15:12-1Ну как минимум там даже не потрудились привести алгоритм выбора location при наличии вложенных location. А то, что приведено, не сказано, что это для частного случая, когда вложенных location нет. Нет, там и не сказано, что это для общего случая, но блин… :) Это немного ввод в заблуждение.
symbix
03.02.2018 17:22+1Черт его знает, мне все понятно, но я пользовался вложенными location-ами еще когда они были задокументированы только на языке С, так что мне сложно судить, насколько оно может быть непонятно. Если знаете, как написать лучше — напишите, патчи на документацию они принимают.
Temtaime
03.02.2018 16:40В пункте 2 лучше объяснить подробнее что такое префиксный location, чтобы избежать путаниц.
Фактически возвращается самый длинный подходящий не регулярный location, если такого нет — идёт поиск по регулярным сверху вниз.vitaliy2 Автор
03.02.2018 17:10-1Предполагается, что читающий уже знает, что такое префиксный location. Это location ( ? ) или (^~).
tgz
03.02.2018 17:45-2Хотели как лучше, а получилось как всегда.
Раз уже замутили схему, где у тебя половина конфига рассматривается как попало, а вторая половина по расположению в конфиге, то могли бы уж тестирование локейшенов сделать. А то сейчас единственный способ — это запуск в debug режиме, прогон запроса ручками и чтение портянки логов.
homm
05.02.2018 14:48Кучу лет пользуюсь nginx, и только сейчас узнал, что location могут быть вложенными! С одной стороны ни разу не было необходимости в таком, с другой предупрежден — значит вооружен. Спасибо!
romy4
почему же тогда пришлось переместить этот кусок
location ~ \.html$ {
try_files $uri =404;
}
над этим
location ~ / {
rewrite . /index.php last;
}
иначе всегда отрабатывал
location ~ /
при запросе к *.html файламvitaliy2 Автор
Перечитайте статью. У Вас один уровень вложенности. В пределах одного уровня вложенности поиск регулярных выражений происходит сверху вниз.
Цитата из статьи:
Но в пределах нескольких уровней мы вначале находим префиксный location, а потом делаем обратный подъём по дереву.
romy4
у меня как-то не сошлось понимание написанного:
«текущим» для этого простого конфига является самый верхний уровень. Тогда почему игнорируется пункт №3?
vitaliy2 Автор
Всё верно, на первой итерации текущий уровень — это верхний. Но ведь в третьем пункте сказано:
В общем, я не понимаю Ваш вопрос. Возможно, Вам стоит перечитать алгоритм. Либо мне попытаться объяснить его более просто, но способов упрощения я пока не знаю.
Где он игнорируется?
romy4
Если уровней всего один, то какой location будет выбран первым? Вот это, если можно, хочется услышать дополнительно в алгоритме.
vitaliy2 Автор
Если уровней один, то всё слишком просто.
Я сейчас добавлю это в статью, т.?к. не все пишут конфиги с вложенными location, а без них алгоритм можно описать значительно проще. Просто я думал, что это уже итак все знают, и интересен был более общий случай.
vitaliy2 Автор
Добавил частный случай с одним уровнем вложенности.
romy4
Это привлечёт внимание тех, кто ищет с одним уровнем вложенности. Заодно разберутся и с тем, когда несколько уровней.
vitaliy2 Автор
Спасибо, думаю, Вы правы. Вначале поставил описание частного случая, а потом уже описание общего случая с вложенными location. Многим вложенные уровни вообще могут быть не нужны, и они сюда пришли за простым алгоритмом, а в итоге получили супер-навороченный алгоритм, в котором нереально разобраться.
vitaliy2 Автор
Ну и на всякий случай скопирую пример из статьи, чтобы было понятнее.
Здесь при запросе /posts/*** в случаях, когда выражение подходит и под первый regexp-location, и под второй regexp-location, приоритет будет отдан второму regexp-location'у, даже несмотря на то, что он идёт после первого.
telkar
Потому что второй локейшен не regexp-location, а префиксный.
Документация гласит:
…
location можно задать префиксной строкой или регулярным выражением. Регулярные выражения задаются либо с модификатором “~*” (для поиска совпадения без учёта регистра символов), либо с модификатором “~” (с учётом регистра). Чтобы найти location, соответствующий запросу, вначале проверяются location’ы, заданные префиксными строками (префиксные location’ы).
…
vitaliy2 Автор
Я имел ввиду второй regexp (среди location'ом он третий по счёту).
А насчёт документации — там не говорится ничего чётко. Вот цитата, которую Вы привели:
Ну вот нашли мы префиксный location, а что дальше? И имелся ли ввиду поиск префиксного location на текущем уровне или сразу на всех? На эти два вопроса и даёт ответ данная статья. А вопросы то очень важные.