mod_rewrite: просмотр списка правил только один раз
У модуля mod_rewrite сервера «Апач» есть особенность, которая иногда портит много крови — после каждого переписывания URL, он просматривает список правил снова. Если ничего не предпринять, иногда происходит зацикливание, что ввергает новичков в ступор.
Мне эта особенность ни разу не пригодилась, но избавляться от неё приходится постоянно. В комментариях на «Хабре» к статье, название которой я вынес в заголовок, подсказали очень хороший способ, добавить первым правилом следующее:
# Don't loop.
RewriteCond %{ENV:REDIRECT_STATUS} !^$
RewriteRule .* - [L]
Если перезапись URL уже происходила, то любой URL оставляем без изменений и следующего цикла обработки уже не происходит.
Вуду, однако... :)
Комментарий для astur.net.ru:
Mod_rewrite довольно понятная фиговина, на деле. Но язык крайне примитивный, делался чтобы просто было написать модуль, а не правила на нём. То, что есть в nginx выглядит и читается лучше.
Выше просто проверяется на непустоту переменная окружения (об этом говорит «EVN:») «REDIRECT_STATUS» (!^$ — это просто «не пустая строка», где «^$» — пустая регулярка, где написано «начало и конец строки»).
Если она не пустая, то разрешается сработать правилу ниже (правило записывается через директиву RewriteRule), в правиле написана регулярка («.*» — «всё что угодно, включая пустоту») и во что она переписывается («-» — это «оставить как было»). «[L]» означает, что правила ниже применять не нужно.
Поскольку, в случае срабатывания RewriteCond и RewriteRule строка URL остаётся без изменений (у нас же «-» указано), то правило «после каждого переписывания URL, он просматривает список правил снова» не выполняется — ведь URL мы переписывали.
Да не, принцип-то понятен, а вот додуматься до такого простого костыля... я год назад решал похожую проблему и так и сдался :(
...а nginx — это да. У него и возможности по реврайтам апачу не уступают. Видимо из-за этого буду переходить на него с lighttpd.
Комментарий для astur.net.ru:
Ну, традиционное решение — делать ещё правила для URL, которых не надо перезаписывать. Например:
RewriteRule ^/index\.php$ — [L]
RewriteRule .* /index.php [L]
Я видел вчера этот пост на хабре и никак не мог понять почему у меня никогда не возникало такой проблемы. А сейчас вспомнил, наконец. Я всегда приписывал условие несуществования файла
Комментарий для alexeyten.ya.ru:
А если я вызову какой-то URL, который реально есть на диске, то что? Или пользователь заведёт какой-нибудь URL, совпадающий с тем, что есть? Роутинг нарушится и скачается/выполнится реальный файл?
Комментарий для Евгения Степанищева:
Реально на диске есть статика (картинки, css и т. п.) и php-скрипты. Соответственно, статика отдастся, скрипт выполниться.
Что значит «пользователь заведёт какой-нибудь URL»?
Комментарий для alexeyten.ya.ru:
Например, на диске лежит файл /lib/common.php, и пользователь решил завести такой же URL в CMS. Кривой пример, но другой придумывать не зачем — это же иллюстрация.
Комментарий для Евгения Степанищева:
Не. Пользователь не может завести такой урл в CMS. Он может заводить только каталоги и «файлы».html. Ну и заливать статику в специально отведённый каталог.
Имена каталогов могут теоретически пересекаться, но пользователь сам себе не враг и зачем ему могут понадобиться каталоги css, js и i, я не знаю :-)
Комментарий для alexeyten.ya.ru:
Мало ли зачем. Да и непонятно зачем ограничивать пользователя созданием только «.html».