Пишу, по большей части, про историю, свою жизнь и немного про программирование.

mod_rewrite: просмотр списка правил только один раз

У модуля mod_rewrite сервера «Апач» есть особенность, которая иногда портит много крови — после каждого переписывания URL, он просматривает список правил снова. Если ничего не предпринять, иногда происходит зацикливание, что ввергает новичков в ступор.

Мне эта особенность ни разу не пригодилась, но избавляться от неё приходится постоянно. В комментариях на «Хабре» к статье, название которой я вынес в заголовок, подсказали очень хороший способ, добавить первым правилом следующее:

# Don't loop.
RewriteCond %{ENV:REDIRECT_STATUS} !^$
RewriteRule .* - [L]

Если перезапись URL уже происходила, то любой URL оставляем без изменений и следующего цикла обработки уже не происходит.

10 комментариев
astur (astur.net.ru) 2009

Вуду, однако... :)

Евгений Степанищев (bolknote.ru) 2009

Комментарий для astur.net.ru:

Mod_rewrite довольно понятная фиговина, на деле. Но язык крайне примитивный, делался чтобы просто было написать модуль, а не правила на нём. То, что есть в nginx выглядит и читается лучше.

Выше просто проверяется на непустоту переменная окружения (об этом говорит «EVN:») «REDIRECT_STATUS» (!^$ — это просто «не пустая строка», где «^$» — пустая регулярка, где написано «начало и конец строки»).

Если она не пустая, то разрешается сработать правилу ниже (правило записывается через директиву RewriteRule), в правиле написана регулярка («.*» — «всё что угодно, включая пустоту») и во что она переписывается («-» — это «оставить как было»). «[L]» означает, что правила ниже применять не нужно.

Поскольку, в случае срабатывания RewriteCond и RewriteRule строка URL остаётся без изменений (у нас же «-» указано), то правило «после каждого переписывания URL, он просматривает список правил снова» не выполняется — ведь URL мы переписывали.

astur (astur.net.ru) 2009

Да не, принцип-то понятен, а вот додуматься до такого простого костыля... я год назад решал похожую проблему и так и сдался :(

...а nginx — это да. У него и возможности по реврайтам апачу не уступают. Видимо из-за этого буду переходить на него с lighttpd.

Евгений Степанищев (bolknote.ru) 2009

Комментарий для astur.net.ru:

Ну, традиционное решение — делать ещё правила для URL, которых не надо перезаписывать. Например:

RewriteRule ^/index\.php$ — [L]
RewriteRule .* /index.php [L]

Lynn «Кофеман» (alexeyten.ya.ru) 2009

Я видел вчера этот пост на хабре и никак не мог понять почему у меня никогда не возникало такой проблемы. А сейчас вспомнил, наконец. Я всегда приписывал условие несуществования файла

Евгений Степанищев (bolknote.ru) 2009

Комментарий для alexeyten.ya.ru:

А если я вызову какой-то URL, который реально есть на диске, то что? Или пользователь заведёт какой-нибудь URL, совпадающий с тем, что есть? Роутинг нарушится и скачается/выполнится реальный файл?

Lynn «Кофеман» (alexeyten.ya.ru) 2009

Комментарий для Евгения Степанищева:

Реально на диске есть статика (картинки, css и т. п.) и php-скрипты. Соответственно, статика отдастся, скрипт выполниться.

Что значит «пользователь заведёт какой-нибудь URL»?

Евгений Степанищев (bolknote.ru) 2009

Комментарий для alexeyten.ya.ru:

Например, на диске лежит файл /lib/common.php, и пользователь решил завести такой же URL в CMS. Кривой пример, но другой придумывать не зачем — это же иллюстрация.

Lynn «Кофеман» (alexeyten.ya.ru) 2009

Комментарий для Евгения Степанищева:

Не. Пользователь не может завести такой урл в CMS. Он может заводить только каталоги и «файлы».html. Ну и заливать статику в специально отведённый каталог.

Имена каталогов могут теоретически пересекаться, но пользователь сам себе не враг и зачем ему могут понадобиться каталоги css, js и i, я не знаю :-)

Евгений Степанищев (bolknote.ru) 2009

Комментарий для alexeyten.ya.ru:

Мало ли зачем. Да и непонятно зачем ограничивать пользователя созданием только «.html».