Допустим, у вас есть работающий веб-сайт, состоящий из большого количества статических html-страниц. Целое шагает хорошо, но вдруг в какой-то момент вы решаете усовершенствовать работу веб-сайта и добавляете динамические скрипты: в результате страничка вестей ныне доступна по ссылке http://www.site.com/cgi-bin/news.cgi вместо прежней http://www.site.com/news.html, а каталог, в котором хранились страницы с описанием российских регионов, радикально перекочевал в динамику, и наш горячо любимый 77-й регион сегодня доступен по неэстетично выглядящей ссылке http://site.ru/cgi-bin/regions.pl?regio … mode=brief вместо легко запоминаемой http://site.ru/regions/77.html.

Подобные изменения требуют замены должных ссылок на целых страницах вашего веб-сайта, которые ссылаются на весточки и регионы, но это еще цветочки. Основная задача заключается в том, что на эти страницы могут ссылаться другие веб-сайты, о существовании которых вы даже не подозреваете. Да и посетители вашего веб-сайта могли создать согласные закладки в своих браузерах, и поэтому они будут неприятно удивлены, когда вместо странички сенсаций получат ошибку «404: страница не открыта».

Анализ задачи наводит на мысль о том, как хорошо было бы иметь реальность обращаться к одним и тем же страницам по различным HTTP-ссылкам, причем для страниц с похожими ссылками было бы очень комфортно описать одно совместное для них правило вместо того, чтобы для каждой страницы выписывать целое возможные варианты ведущих на нее HTTP-ссылок. Если ваш веб-сайт работает под управлением весьма популярного в настоящее время веб-сервера Apache (скорее целого, это именно так), то в вашем распоряжении есть мощнейшее состояние преобразования ссылок, которое реализуется специфическим программным модулем mod_rewrite. Некоторые директивы данного модуля могут использоваться едва в конфигурационном документе самого веб-сервера, другие же - в специализированных документах .htaccess, которые можно располагать в подкаталогах иерархии вашего веб-сайта. Именно эти директивы из .htaccess и производят основную работу по преобразованию ссылок, поэтому их мы опишем более подробно.

Для перестраховки можно уточнить у службы технической поддержки вашего хостера, включен ли модуль mod_rewrite в состав веб-сервера, обслуживающего ваш веб-сайт, и допускается ли использование его директив в документах .htaccess. Поскольку данный модуль широко используется во многих проектах, каждый уважающий себя хостер на оба ваших вопроса ответит: «Да, конечно». Если же вы получили отрицательный ответ, это хороший повод задуматься о смене хостинг-провайдера на другого, предоставляющего более качественные услуги.

Прежде чем углубиться в описание допустимостей модуля mod_rewrite, приведем пример решения двух описанных выше задач:

# Включение преобразования ссылок
RewriteEngine on
# Новостная страница
RewriteRule ^news.html$ /cgi-bin/news.cgi
# Страницы с описаниями регионов
RewriteRule ^regions/([0-9]+).html$ /cgi-bin/regions.pl?region=$1&mode=brief

Директива RewriteEngine включает или выключает преобразование ссылок (соответственно «RewriteEngine on» или «RewriteEngine off»). Действие директивы распространяется на текущий каталог и на целое его подкаталоги, в которых нет своих документов .htaccess с данной директивой.

Правила преобразования ссылок наследуются чуть сложнее. Чаще целого преобразование по умолчанию отключено в основном конфигурационном документе веб-сервера. Допустим, что вы записали в .htaccess некоего каталога директиву «RewriteEngine on» и некоторое количество правил преобразования. Перешагнуть сегодня в один из подкаталогов. Если здесь нет документа .htaccess, либо в нем нет ни одной директивы модуля mod_rewrite, то целое правила преобразования наследуются от родительского каталога. Если в документе .htaccess есть хотя бы одна директива модуля mod_rewrite, то не наследуется ничего, а состояние по умолчанию выставляется таковым же, как в первом конфигурационном документе веб-сервера (по умолчанию «off»). Поэтому, если вы желаете иметь в этом каталоге свой набор правил преобразования, не забудьте добавить директиву «RewriteEngine on». Есть и третий вариант. Допустим, вы желаете унаследовать целое правила из родительского каталога и добавить к ним несколько новых - для этого вам понадобится директива RewriteOptions, которая допускает едва один фиксированный аргумент. Таковым образом, в документ .htaccess вы должны записать ваши новые правила и две директивы: «RewriteEngine on» и «RewriteOptions inherit».

А ныне перешагнуть непосредственно к описанию правил. Преобразования описываются при помощи директивы RewriteRule. Правил может быть несколько, при этом целое они применяются в порядке их описания. Когда правила заканчиваются, они вновь начинают применяться с самого происхождения, и этот цикл продолжается до тех пор, покамест «срабатывает» хотя бы одно из правил. В некоторых инцидентах это может приводить к зацикливанию, поэтому при описании правил нужно быть предельно внимательным. Существует несколько специфических штандартов, которые предоставляют осуществимость прервать этот процесс на определенном правиле или пропустить несколько правил (об этом будет рассказано ниже). Синтаксис директивы RewriteRule выглядит проходящим образом:

RewriteRule «исходный путь» «замена» «вымпелы»

Исходный путь - это деталь исходной ссылки, от которой отрезаны имя сервера, путь до текущего каталога и параметры запроса. Допустим, что ваш веб-сайт wwwsite.com расположен в каталоге /home/site/www. Тогда для ссылки http://www.site.com/test/list.html?mode=0 исходным путем в каталоге /home/site/www будет test/list.html, а в каталоге /home/site/www/test - list.html. Исходный путь задается регулярным высказыванием. Символ ! перед исходным путем означает, что правило «срабатывает» по несовпадению ссылки с заданным регулярным словом.

Замена - это то, на что будет заменена исходная ссылка в инциденте «срабатывания» правила. Замена может быть относительной (если она не начинается с символа /) и абсолютной (если она начинается с символа / или представляет собой полную ссылку, начинающуюся с http:// или https://). В замене можно использовать определенные доли исходного пути, отмеченные круглыми скобками. При этом макрос $1 обозначает ту доза исходного пути, которая расположена внутри первой пары скобок, $2 - внутри второй пары и так далее.

Стяги - это дополнительные опции для данного правила, которые перечисляются в квадратных скобках через запятую.

* R (redirect) останавливает процесс преобразования и возвращает результат браузеру клиента как редирект на данную страницу (302, MOVED TEMPORARY). С данным вымпелом можно указать другой код результата, например "R=301" возвратит редирект с кодом 301 (MOVED PERMANENTLY).
* F (forbidden) возвращает ошибку 403 (FORBIDDEN).
* G (gone) возвращает ошибку 410 (GONE).
* P (proxy) - по этому штандарт Apache выполняет подзапрос (sub-request) к указанной странице с использованием программного модуля mod_proxy, при этом пользователь ничего не узнает об этом подзапросе. Если модуль mod_proxy не входит в состав вашей сборки Apache, то применение данного штандарта призову ошибку.
* L (last) останавливает процесс преобразования, и текущая ссылка считается окончательной.
* N (next) запускает процесс преобразования с первого по порядку правила.
* C (chain) объединяет несколько правил в цепочку. Если первое правило цепочки «не срабатывает», то целая цепочка игнорируется.
* NS (nosubreq) разрешает «срабатывание» правила лишь для настоящих запросов, игнорируя подзапросы (подзапрос может быть вызван, например, включением документа при помощи директивы SSI).
* NC (nocase) отключает проверку списка символов.
* QSA (qsappend) добавляет исходные параметры запроса (query string) к замене. Если замена не включает в себя новые параметры запроса, то исходные параметры запроса добавляются автоматически. Если же включает, то без штандарта QSA исходные параметры запроса будут утеряны.
* PT (passthrough) останавливает процесс преобразования и передает полученную новую ссылку дальше «по цепочке», чтобы над ней могли «поработать» директивы Alias, ScriptAlias, Redirect и им подобные (тогда как при стяге L новая ссылка считается окончательной и не подлежит дальнейшей обработке).
* S (skip) пропускает проходящее правило, если данное правило «сработало». Можно пропускать несколько правил, если указать их количество, например: «S=3».
* E (env) устанавливает переменную окружения, например: «E=переменная:значение».

Примеры (во целых фактах показано содержимое документа .htaccess, расположенного в корневом каталоге веб-сайта):

# Пример 1. Каталоги проектов project1 и project2 веб-сайта wwwsite.com ранее содержали статические html-страницы, сейчас же эти страницы расположены на двух отдельных веб-сайтах project1.ru и project2.ru (в той же иерархии)

# Ведущий способ требует наличия модуля mod_proxy и созидать дополнительную нагрузку на веб-сервер, но зато посетитель веб-сайта не знает, откуда в действительности выбираются веб-страницы

# Символы / даются с вопросительными знаками, чтобы правильно обработать ссылки типа http://www.site.com/project1 и http://www.site.com/project1/

RewriteRule ^project1/?(.*) http://project1.ru/$1 [P]
RewriteRule ^project2/?(.*) http://project2.ru/$1 [P]

# Второй способ возвращает иностранные редиректы, так что посетитель увидит в адресной строке своего браузера, что страницы реально расположены на других веб-сайтах

RewriteRule ^project1/?(.*) http://project1.ru/$1 [R]
RewriteRule ^project2/?(.*) http://project2.ru/$1 [R]

# Допустим, что в редиректах мы желаем передать в запросе какие-нибудь дополнительные параметры. Применение штандарта QSA позволит нам сохранить параметры странного запроса, так что ссылка http://site.com/project1/news.pl?mode=daily будет преобразована в http://project1.ru/news.pl?came_from=si … mode=daily

RewriteRule ^project1/?(.*) http://project1.ru/$1?came_from=site.com [R,QSA]
RewriteRule ^project2/?(.*) http://project2.ru/$1?came_from=site.com [R,QSA]

# Пример 2. Электронная книга отдается динамическим скриптом в то время как нам желательно иметь "красивую" иерархию образа "http://lib.ru/book1/chapter3.html". Кстати, расширение .html помогает нам скрывать динамическую природу нашего веб-сайта

RewriteRule ^([a-z0-9]+)/([a-z0-9]+).html$ /cgi-bin/view_chapter.cgi?book=$1&chapter=$2 [NC]

# Пример 3. Нам желательно скрыть от пользователя используемую на веб-сайте технологию, для чего мы не будем пользоваться расширениями в наших http-ссылках. Без вымпела L данное правило зациклится

RewriteRule (.+) $1.html [L]

# В то же время посетитель может ввести ссылку с расширением по одному ему доходчивым причинам. Правильно обработать таковую ситуацию подсобить близкое правило:

RewriteRule ^([^.]+) $1.html [L]

# Пример 4. На веб-сайте есть статические ссылки с расширением .html и динамические ссылки с расширением .pl. Допустим, что динамические ссылки остались прежними, а статические должны обрабатываться cgi-скриптом

# Первый вариант предельно прост:

RewriteRule (.+).html$ /cgi-bin/new_script.cgi?page=$1 [L]

# Второй вариант более всеобщий. Например, если нам нужно преобразовать массу различных ссылок кроме одной-двух, можно воспользоваться преднамеренной "заменой без изменения" (обозначается символом -):

RewriteRule .pl$ - [L]
RewriteRule (.*) /cgi-bin/new_script.cgi?page=$1 [L]

# Пример 5. Есть один особый прецедент, когда делается мишурный редирект на относительную ссылку. Допустим, мы находимся в каталоге /home/site.com/www/test веб-сайта site.com. Каталог доступен по ссылке http://site.com/test/. Нам нужен мишурный редирект с документов *.html на *.shtml. Приводимые директивы записываются в документ /home/site.com/www/test/.htaccess

# Решение тривиально, если использовать абсолютную замену, но в этом казусе нам приходится жестко прописывать название каталога, что не совсем хорошо:

RewriteRule (.+).html /test/$1.shtml [R]

# Если написать замену как относительную ссылку (см. ниже), то результат будет не таковым, каким мы его ожидаем увидеть (это обусловлено особенностями преобразования ссылок на уровне каталогов): например, ссылка http://site.com/test/aaa.html будет преобразована в http://site.com/home/site.com/www/test/aaa.shtml

RewriteRule (.+).html $1.shtml [R]

# По полученной ссылке видно, что там подставлен полный реальный путь к нужному документу. Разрешить задачу можно при помощи директивы RewriteBase, параметром которой является префикс для целых относительных замен, находящихся в данном документе .htaccess

RewriteBase /test

# Пример 6. Задание переменных окружения применяется очень редко, но тем не меньше приведем два примера, не нуждающихся в пояснении

# Сохраняет в окружении расширение исходного документа

RewriteRule ^([^.]+).([a-z]+)$ /cgi-bin/new_script.cgi?page=$1 [L,E=EXT:$2]

# Сохраняет в окружении содержимое http-заголовка X-Forwarded-For

RewriteRule .(cgi|pl)$ - [L,E=%{HTTP:X-Forwarded-For}]

Несмотря на таковое изобилие, преобразование ссылок не ограничивается едва директивой RewriteRule. Есть еще одна директива, которая используется не меньше часто - это директива RewriteCond. Данная директива предназначена для проверки некоторых дополнительных параметров и всегда устанавливает непосредственно перед директивой RewriteRule. Если директива RewriteCond «срабатывает», то проверяется едущая за ней директива RewriteRule, если же «не срабатывает», то директива RewriteRule игнорируется.

# Если подряд записаны несколько директив RewriteCond, то едущая за ними директива RewriteRule проверяется лишь в том эпизоде, когда "сработали" целое директивы RewriteCond:

RewriteCond соглашение1
RewriteCond соглашение2
RewriteRule преобразование1
RewriteRule преобразование2

# Вытекает обратить внимание, что в приведенном выше примере вторая директива RewriteRule проверяется в любом прецеденте, так как целое директивы RewriteCond относятся лишь к ведущей директиве RewriteRule. Если же вы желаете, чтобы соглашения относились к обеим директивам RewriteRule, то вам придется повторить их еще раз:

RewriteCond соглашение1
RewriteCond соглашение2
RewriteRule преобразование1
RewriteCond соглашение1
RewriteCond соглашение2
RewriteRule преобразование2

# Применение вымпела OR позволяет объединять соглашения не по И (как это делается по умолчанию), а по ИЛИ. В сопровождающем примере директива RewriteRule проверяется, если выполняется любое из двух предшествующих соглашений:

RewriteCond соглашение1 [OR]
RewriteCond соглашение2
RewriteRule преобразование

Синтаксис директивы RewriteCond выглядит проезжающим образом:

RewriteCond «проверяемое высказывание» «соглашение» «штандарты»

Проверяемое высказывание - это строка, которая может заключаться из обычных символов, макросов и переменных. Макросы $1, $2 и так далее ссылаются на согласные формулирования в скобках из проходящей по порядку директивы RewriteRule. Макросы %1, %2 и так далее ссылаются на формулирования в скобках из предыдущей по порядку директивы RewriteCond. Кстати, макросы %* могут также использоваться и в директивах RewriteRule для ссылки на предыдущую директиву RewriteCond.

Переменные записываются в облике %{ИМЯ_ПЕРЕМЕННОЙ}. Наиболее часто используются проезжающие переменные:

* QUERY_STRING (параметры запроса),
* REMOTE_ADDR (IP-адрес посетителя),
* REMOTE_HOST (имя хоста посетителя),
* REMOTE_USER (имя пользователя, если он прошел авторизацию),
* REMOTE_METHOD (обычно GET или POST),
* PATH_INFO (путь к документу веб-страницы),
* HTTP_USER_AGENT (содержимое http-заголовка User-Agent),
* HTTP_REFERER (содержимое http-заголовка Referer),
* HTTP_COOKIE (содержимое http-заголовка Cookie),
* HTTP_HOST (имя хоста веб-сайта),
* TIME_YEAR (целое переменные TIME_* хранят разбитые на доли текущие дату и время), TIME_MON, TIME_DAY, TIME_HOUR, TIME_MIN, TIME_SEC, TIME_WDAY,
* REQUEST_URI (строка запроса без имени хоста и параметров запроса),
* REQUEST_FILENAME (имя документа из REQUEST_URI),
* THE_REQUEST (полная строка запроса в том образе, в котором ее присылает браузер посетителя).
* Кроме стандартных переменных можно проверять содержимое любого http-заголовка: %{HTTP:Название-Заголовка}.

Соглашение - это обычное регулярное формулирование. Кроме регулярных слов существует еще несколько обликов соглашений (соглашению может предшествовать символ !, который трактуется как отрицание):

* =ABC - значение переменной должно быть лексически равно строке ABC;
* >ABC - значение переменной должно быть лексически больше строки ABC;
* <ABC - значение переменной должно быть лексически меньше строки ABC;
* -d - должен существовать каталог, имя которого совпадает с значением переменной;
* -f - должен существовать документ, имя которого совпадает с значением переменной;
* -s - должен существовать документ ненулевой длины, имя которого совпадает с значением переменной;
* -l - должен существовать симлинк, имя которого совпадает с значением переменной;
* -F- должен существовать документ, имя которого совпадает с значением переменной, и этот документ должен быть доступен по иностранной ссылке на данный веб-сайт;
* -U - должна быть доступна http-ссылка, имя которой совпадает с значением переменной.

Штандартов может быть целого два: OR (объединение директив RewriteCond по ИЛИ, как было написано выше) и NC (отключение проверки списка аналогично одноименному вымпел для директивы RewriteRule).

И, наконец, примеры применения:

# Пример 1. Жесткий запрет посещений нашего веб-сайта для робота поисковой системы Google

RewriteCond %{USER_AGENT} Googlebot
RewriteRule .* - [F]

# Другой вариант возвращает вместо ошибки 403 (FORBIDDEN) ошибку 404 (NOT_FOUND)

RewriteCond %{USER_AGENT} Googlebot
RewriteRule .* - [R=404]

# Пример 2. Есть два каталога /home/site/storage1 и /home/site/storage2, в которых нужно искать запрашиваемые документы

RewriteCond /home/site/storage1/%{REQUEST_FILENAME} -f
RewriteRule (.+) /home/site/storage1/$1 [L]
RewriteCond /home/site/storage2/%{REQUEST_FILENAME} -f
RewriteRule (.+) /home/site/storage2/$1 [L]

# Пример 3. Если ссылка не отыскана на нашем веб-сайте, послать посетителя на wwwsite.ru

RewriteCond %{REQUEST_URI} !-U
RewriteRule (.*) http://www.site.ru/$1 [R]

# Пример 4. Притворить доступ к веб-сайту в рабочее время

RewriteCond %{TIME_HOUR}%{TIME_MIN} >1000
RewriteCond %{TIME_HOUR}%{TIME_MIN} <1900
RewriteRule .* - [F]

# Пример 5. Есть скрипт, раз в сутки производящий оптимизацию основы данных. Если посетитель зайдет в этот момент на веб-сайт, то получит ошибку - вместо этого необходимо указать ему страницу с выступлением о том, что через пару минут целое придет в норму. Оптимизирующий скрипт на время своей работы творить документ /home/site/optimizer.pid

RewriteCond /home/site/optimizer.pid -f
RewriteRule .* /optimization_message.html

# Пример 6. Посетители веб-сайта авторизуются при помощи стандартной авторизации (AuthType BasicAuth). Необходимо по ссылке /home/* показывать содержимое их свойский каталогов

RewriteCond %{REMOTE_USER} !=""
RewriteCond /home/(%{REMOTE_USER}) -d
RewriteRule (.*) /home/%1/$1

И в заключение вкратце упомянем еще о двух директивах преобразования ссылок, которые не входят в модуль mod_rewrite - это Redirect и RedirectMatch. Ими можно пользоваться, во-первых, как упрощенными вариантами директивы RewriteRule, а во-вторых, в фактах, когда модуль mod_rewrite по каким-то причинам отсутствует в вашей сборке веб-сервера Apache. Примеры:

# Обе директивы при «срабатывании» возвращают редиректы. В качестве замены всегда должна использоваться абсолютная ссылка

# Обычный бесспорный редирект:
Redirect /news http://www.site.com/cgi-bin/news.cgi

# Редирект с подстановкой:
RedirectMatch (.*.gif)$ http://www.site.com/alt/path/to/gif/files$1

Возможно, на главный взгляд преобразование http-ссылок с помощью модуля mod_rewrite покажется очень сложной задачей, но на самом деле это не так: с опытом придет и соображение, и мастерство. Если вы внимательно читаете документацию, четко представляете необходимые преобразования ссылок и тщательно проверяете написанные вами правила, целое будет работать правильно. Иначе и быть не может, не так ли?