Программное обеспечение.

Аpache mod_rewrite: понять принцип

Александр » 03 ноя 2014, 19:58

В интернете достаточно много информации про модуль mod_rewrite. И вроде можно за пару дней не спеша разобраться в том, что делают директивы RewriteCond, RewriteRule, понять как использовать в них backreferences ($N берутся из RewriteRule, %N — из последнего совпавшего RewriteCond), использовать регулярные выражения, добавив к ним ещё функционал в виде знака '!' для отрицания..
Пытливый человек после изучения документации http://httpd.apache.org/docs/current/mo ... write.html или http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html может обратить внимание на 2 вещи:
  1. Начинает сбивать с толку то, что в RewriteCond, идущих по тексту в конфигурационном файле над RewriteRule всё равно уже есть доступ к backreferences ($N) из RewriteRule.
  2. И почти не понятно предназначение директивы RewriteBase.
Непонимание этих вещей приводит к тому, что как только задача, которую хотят решить с помощью mod_rewrite отклоняется от шаблонных примеров, она превращается в мучительный перебор вариантов с восклицаниями типа: "Ну почему, чёрт возьми, он не работает, что не так???!!".

Эта статья подразумевает, что вы более-менее в курсе всего выше сказанного. Её цель не предоставить набор готовых решений ( ибо это невозможно ), а показать направление, в котором нужно смотреть при возникновении подобных проблем. Включение мозгов никто не отменял!

И так, вникаем в документацию http://httpd.apache.org/docs/current/rewrite/tech.html.
Переводя с Английского на Русский, и излагая максимально доходчиво, можно дать следующее объяснение принципа работы mod_rewrite, и что он хочет видеть в качестве грамотных команд.

Для наглядности я скопировал сюда блок-схему обработки запроса модулем mod_rewrite .
Аpache mod_rewrite: понять принцип
Блок-схема обработки запроса модулем mod_rewrite.
rewrite_process_uri.png (104.3 Кб) Просмотров: 8642


Что мы видим из документации и схемы:
Несмотря на то, что в конфигурационном файле в каждом блоке инструкций для mod_rewrite сначала идут директивы RewriteCond, а замыкает их директива RewriteRule, обработка запроса начинается с того, что модуль последовательно просматривает все директивы RewriteRule из конфигурационного файла, и только в случае совпадения правила, указанного в RewriteRule, он начинает проверять привязанный к нему набор правил из RewriteCond, таким образом достигается оптимизация производительности, чтобы не выполнять заранее бесполезные проверки из RewriteCond.
Это как раз и дает ответ на вопрос вопрос из пункта 1. по поводу того, откуда сразу берутся backreferences ($N) из RewriteRule.

Также важно понимать, с чем работают директивы RewriteRule:

Apache обрабатывает запрос поэтапно, эти этапы называются "хуками" (hooks). Mod_rewrite активизируется при отработке 2-х хуков:
1-й этап (hook) называется "URL-to-filename".
2-й этап (hook) называется "Fixup".

На первом этапе (URL-to-filename) происходит следующее:
Если директива указана в конфигурационном файле сервера уровня httpd.conf или vhosts.conf, то первому RewriteRule передается запрошенный к серверу URL с отрезанным именем протокола, порта и параметрами запроса (те, что идут после вопросительного знака).
Т. е. если запрос был вида "https://tamirov.ru/programmirovanie/seo-audit-sajtov.html?start=1", то первому RewriteRule, если он есть в конфигурационном файле сервера, будет передана строка "/programmirovanie/seo-audit-sajtov.html" (обратите внимание на ведущий слэш!). Далее этот RewriteRule уже будет работать с этой строкой, преобразуя её в другую строку и передавая её уже измененной последующим RewriteRule из этого файла, если таковые есть.

Также важно заменить, что в конце этой фазы происходит формирование переменной REQUEST_URI. Дальнейшие простые преобразования, происходящие в RewriteRule-ах на следующей фазе (Fixup) уже не меняют значение переменной REQUEST_URI ( $_SERVER["REQUEST_URI"], %{REQUEST_URI} ).

На втором этапе (Fixup):
Как написано в документации, в процессе этой фазы обработки запроса сервер начинает чтение директив из файлов .htaccess, секций <Directory> конфигурационных файлов, проводит процесс авторизации и т. д.
При обработке запроса на этой фазе в RewriteRule передается вышеуказанная переработанная строка запроса, от которой отрезан префикс, соответствующий пути в файловой системе до данного файла .htaccess. Кто сомневается, вот цитата, читаем документацию:
When using the rewrite engine in .htaccess files the per-directory prefix (which always is the same for a specific directory) is automatically removed for the RewriteRule pattern matching and automatically added after any relative (not starting with a slash or protocol name) substitution encounters the end of a rule set. See the RewriteBase directive for more information regarding what prefix will be added back to relative substitutions.

......

In per-directory context (i.e., within .htaccess files and Directory blocks), these rules are being applied after a URL has already been translated to a filename. Because of this, the URL-path that mod_rewrite initially compares RewriteRule directives against is the full filesystem path to the translated filename with the current directories path (including a trailing slash) removed from the front.

Причем, префикс отрезается вместе с последним слэшем. Отрезанный префикс сохраняется в переменной RewriteBase Таким образом на этой фазе, например, если обрабатываются директивы из файла /var/www/foo/.htaccess, а запрос представляет собой строку вида /foo/bar/baz/test.html, то сработает RewriteRule с проверкой вида ^bar/baz. Главное понять, что в первом RewriteRule строка сопоставления не может начинаться со слэша.

Таким образом происходит последовательная обработка всех правил RewriteRule и как только они все переварились наступает важный, но тонкий момент. К полученной строке, если она выглядит как относительный путь (т. е. без протокола или слэша в начале) добавляется префикс RewriteBase, и происходит внутренний редирект, т. е. редирект по URI (по пути в файловой системе ). И снова происходит обработка попавшихся по пути файлов .htaccess.
Если строка выглядит как абсолютный путь (начинается с протокола или слэша в начале), то происходит внешний редирект т. е. как по обычному web-URL.
Отсюда получаются следующие выводы:

Директива RewriteBase важна когда запрашиваемый URL-адрес не соответствует реальному файловому пути на сервере, вот пример из документации, подмена виртуального адреса на реальный при помощи mod_alias (директива Alias занимается отображением URL в физический путь в файловой системе):
Код: Выделить всё
DocumentRoot /var/www/example.com
Alias /myapp /opt/myapp-1.2.3
<Directory /opt/myapp-1.2.3>
    RewriteEngine On
    RewriteBase /myapp/
    RewriteRule ^index\.html$  welcome.html
</Directory>

Также директиву RewriteBase потребуется прописать явно, если в правиле RewriteRule задается относительный адрес, т. е. если правило замены не начинается с протокола или слэша и указан флаг редиректа [R]. Если в данном случае не указать явно RewriteBase, то мы на выходе получим внешний редирект на адрес с префиксом в виде физического пути в файловой системе.
Для примера:
Пусть у нас, в конфигурационном файлк виртуального хоста указано DocumentRoot /var/www/example.com, файл .htaccess лежит в /var/www/example.com/cms/ . В этом .htaccess прописано правило:
RewriteRule ^index\.html$ welcome.html [R=301,L]
Если мы не зададим явно директиву RewriteBase, то запрос будет внешне перенаправлен на адрес httр://example.com/var/www/example.com/cms/welcome.html, что не то, что нужно.
Для того чтобы это исправить достаточно указать директиву:
RewriteBase /cms/
И получаем перенаправление на httр://example.com/cms/welcome.html

Изменение переменной REQUEST_URI ($_SERVER["REQUEST_URI"], %{REQUEST_URI}) через .htaccess.


Как я писал выше, при манипуляциях с RewriteRule в конфигурационных файлах уровня директории и внутренних редиректах, значение переменной REQUEST_URI ($_SERVER["REQUEST_URI"], %{REQUEST_URI}) не изменяется. Вы не сможете напрямую в .htaccess переопределить какую-либо системную переменную, например, используя синтаксис [E=VAR:VAL]. Но иногда это сделать хочется. Здесь можно посоветовать либо это делать скриптом/cgi-программой. Либо можно все-таки сделать это через .htaccess, если у вас включен модуль mod_proxy.
Тогда вы сможете воспользоваться флагом [P].
Флаг [P] вызовет внутреннее проксирование запроса, что приведет к изменению переменной REQUEST_URI.
Например:
RewriteRule ^abc/index\.html$ /bcd/welcome.html [P]

RewriteLog, RewriteLogLevel — логирование преобразований.

Полезно иногда включить логирование операций, производимых модулем mod_rewrite.
В конфигурационных файлах уровня httpd.conf или vhosts.conf можно задать имя файла для логирования директивой RewriteLog и уровень детализации логирование директивой RewriteLogLevel (от 0 до 9, 0 — отключает логирование).
Ну, и последний совет, который всегда поможет, но он для неленивых. Если какой-то нюанс все-таки не понятен, всегда можно открыть исходники модуля на C, даже если вы его почти не знаете, интуитивно найти блок кода и посмотреть, что же он делает. Т. к. я считаю, что официальная документация к модулю mod_rewrite неполна.
Александр
 
Сообщения: 397
Зарегистрирован: 20 мар 2014, 17:05

Вернуться в Софт