Преобразование ссылок с помощью Apache
🕛 27.10.2006, 12:12
Допустим, у вас есть работающий веб-сайт, состоящий из большого количества статических html-страниц. Все идет хорошо, но вдруг в какой-то момент вы решаете усовершенствовать работу веб-сайта и добавляете динамические скрипты: в результате страничка новостей теперь доступна по ссылке http://www.site.com/cgi-bin/news.cgi вместо прежней http://www.site.com/news.html, а каталог, в котором хранились страницы с описанием российских регионов, полностью перекочевал в динамику, и наш горячо любимый 77-й регион теперь доступен по неэстетично выглядящей ссылке http://site.ru/cgi-bin/regions.pl?region=77&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 «исходный путь» «замена» «флаги»
Исходный путь - это часть исходной ссылки, от которой отрезаны имя сервера, путь до текущего каталога и параметры запроса. Допустим, что ваш веб-сайт www.site.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 веб-сайта www.site.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=site.comamode=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. Если ссылка не найдена на нашем веб-сайте, отправить посетителя на www.site.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 покажется очень сложной задачей, но на самом деле это не так: с опытом придет и понимание, и мастерство. Если вы внимательно читаете документацию, четко представляете необходимые преобразования ссылок и тщательно проверяете написанные вами правила, все будет работать правильно. Иначе и быть не может, не так ли?
hostinfo.ru,