Полное руководство по mod_rewrite (часть 6): Продвинутые техники применения mod_rewrite

Оглавление. Полное руководство по mod_rewrite

1. Как включить и как работает mod_rewrite

2. Регулярные выражения mod_rewrite

3. Флаги RewriteRule

4. Директива RewriteCond

5. Частые случаи и примеры использования mod_rewrite

6. Продвинутые техники

6.1 Поиск страниц в нескольких папках

6.2 Перенаправление на географически распределенные серверы

6.3 Показ содержимого в зависимости от браузера

6.4 Перемещенный DocumentRoot

6.5 Вспомогательный ресурс

6.6 Переписать строку запроса

6.7 Генерация контента на лету

6.8 Балансировка нагрузки

6.9 Структурированные Userdirs

6.10 Перенаправление анкеров

6.11 Зависимая от времени перезапись

6.12 Установка переменных среды на основе URL-адресов

6.13 Динамические массовые виртуальные хосты с mod_rewrite

6.13.1 Виртуальные хосты для произвольных имён хостов

6.13.2 Динамические виртуальные хосты с помощью mod_rewrite

6.13.3 Использование отдельного файла конфигурации виртуального хоста

6.14 Использование mod_rewrite для проксирования

7. Директива RewriteMap

8. Директива RewriteOptions, технические подробности, когда НЕ использовать mod_rewrite


Поиск страниц в нескольких папках

Описание:

Конкретный ресурс может существовать в одном из нескольких мест, и когда ресурс запрашивается, мы хотим искать его по всем этим папкам. Возможно, мы недавно перестроили нашу структуру каталогов, разделив контент на несколько мест.

Решение:

Чтобы найти ресурс следующий набор правил ищет в двух каталогах, и, если не найдено ни в одном из этих мест, попытается просто выполнить его из запрошенного местоположения.

RewriteEngine on

#   сначала пробуем найти в папке dir1/...
#   ...и если нашли, останавливаемся:
RewriteCond         "%{DOCUMENT_ROOT}/dir1/%{REQUEST_URI}"  -f
RewriteRule "^(.+)" "%{DOCUMENT_ROOT}/dir1/$1"  [L]

#   затем пытаемся найти его в папке dir2/...
#   ...и если нашли, останавливаемся:
RewriteCond         "%{DOCUMENT_ROOT}/dir2/%{REQUEST_URI}"  -f
RewriteRule "^(.+)" "%{DOCUMENT_ROOT}/dir2/$1"  [L]

#   затем продолжаем для других Alias или ScriptAlias директив,
#   и т.п.
RewriteRule   "^"  "-"  [PT]

Перенаправление на географически распределенные серверы

Описание:

У нас есть многочисленные зеркала нашего веб-сайта, и мы хотим перенаправить людей на тот, который находится в стране, где они находятся.

Решение:

Рассматривая имя хоста запрашивающего клиента, мы определяем, из какой страны они пришли. Если мы не сможем выполнить поиск по их IP-адресу, мы вернемся к серверу по умолчанию.

Мы будем использовать директиву RewriteMap для создания списка серверов, которые мы хотим использовать.

HostnameLookups on
RewriteEngine on
RewriteMap    multiplex         "txt:/path/to/map.mirrors"
RewriteCond   "%{REMOTE_HOST}"  "([a-z]+)$" [NC]
RewriteRule   "^/(.*)$"  "${multiplex:%1|http://www.example.com/}$1"  [R,L]

Карта мультиплексирования (файл map.mirrors):

de http://www.example.de/
uk http://www.example.uk/
com http://www.example.com/
##EOF##

Обсуждение

Этот набор полагается на установленную HostNameLookups на on, что может стать значительным ударом по производительности.

Директива RewriteCond захватывает последнюю часть имени хоста запрашивающего клиента – код страны, а следующий RewriteRule использует это значение для поиска соответствующего хоста зеркала в файле карты.

Показ содержимого в зависимости от браузера

Описание:

Мы хотим предоставить различный контент в зависимости от браузера или агента-пользователя, который запрашивает контент.

Решение:

На основе HTTP-заголовка «User-Agent» мы должны решить, какой контент будет показан. Следующая конфигурация делает следующее: Если HTTP-заголовок «User-Agent» содержит «Mozilla/3», страница foo.html переписывается в файл foo.NS.html, и переписывание останавливается. Если браузер является «Lynx» или «Mozilla» версии 1 или 2, URL-адрес становится foo.20.html. Все остальные браузеры получают страницу foo.32.html. Это делается с помощью следующего набора правил:

RewriteCond "%{HTTP_USER_AGENT}"  "^Mozilla/3.*"
RewriteRule "^foo\.html$"         "foo.NS.html"          [L]

RewriteCond "%{HTTP_USER_AGENT}"  "^Lynx/" [OR]
RewriteCond "%{HTTP_USER_AGENT}"  "^Mozilla/[12]"
RewriteRule "^foo\.html$"         "foo.20.html"          [L]

RewriteRule "^foo\.html$"         "foo.32.html"          [L]

Перемещенный DocumentRoot

Описание:

Обычно DocumentRoot веб-сервера напрямую относится к URL-адресу «/». Но часто эти данные на самом деле не являются приоритетом высшего уровня. Например, вы можете захотеть, чтобы посетители при первом входе на сайт попадали в поддиректорию /about/. Это может быть выполнено с использованием следующего набора правил:

Решение

Мы перенаправляем URL / на /about/:

RewriteEngine on
RewriteRule   "^/$"  "/about/"  [R]

Обратите внимание, что это также можно использовать с помощью директивы RedirectMatch:

RedirectMatch "^/$" "http://example.com/about/"

Также обратите внимание, что пример перезаписывает только корневой URL. То есть, он переписывает запрос на http://example.com/, но не запрос на http://example.com/page.html. Если вы действительно изменили свой корневой каталог документа – то есть, если весь ваш контент на самом деле находится в этом подкаталоге, гораздо предпочтительнее просто изменить директиву DocumentRoot или переместить весь контент в один каталог, а не переписывать URL-адреса.

Вспомогательный ресурс

Описание:

Вы хотите, чтобы один ресурс (скажем, определенный файл, например index.php) обрабатывал все запросы, поступающие в конкретный каталог, за исключением тех, которые должны идти на существующий ресурс, такой как изображение или файл css.

Решение:

Начиная с версии 2.2.16 вы должны использовать директиву FallbackResource для этого:

<Directory "/var/www/my_blog">
  FallbackResource "index.php"
</Directory>

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

<Directory "/var/www/my_blog">
  RewriteBase "/my_blog"

  RewriteCond "/var/www/my_blog/%{REQUEST_FILENAME}" !-f
  RewriteCond "/var/www/my_blog/%{REQUEST_FILENAME}" !-d
  RewriteRule "^" "index.php" [PT]
</Directory>

Если, с другой стороны, вы хотите передать запрошенный URI в качестве аргумента строки запроса в index.php, вы можете заменить RewriteRule следующим:

RewriteRule "(.*)" "index.php?$1" [PT,QSA]

Обратите внимание, что эти наборы правил можно использовать в файле .htaccess, а также в блоке <Directory>.

Переписать строку запроса

Описание:

Вы хотите захватить определенное значение из строки запроса и либо заменить его, либо включить его в другой компонент URL-адреса.

Решения:

Многие из решений в этом разделе будут использовать одно и то же условие, которое оставляет совпадающее значение в обратной ссылке %2 . %1 – это начало строки запроса (вплоть до интересующего ключа), а %3 – это остаток. Чтобы быть гибким и избегать двойных «&&» в подстановках условие получилось немного сложным.

Это решение удаляет соответствующий ключ и значение:

# Удалить mykey=???
RewriteCond "%{QUERY_STRING}" "(.*(?:^|&))mykey=([^&]*)&?(.*)&?$"
RewriteRule "(.*)" "$1?%1%3"

В приведённом выше решении в RewriteCond создаются три обратные ссылки: первая (%1) содержит то, что до mykey, вторая – содержит значение mykey, третья (%3) содержи значение других переменных, которые после mykey.

В результате перезаписи адрес до строки запроса остаётся не изменным (это обратная ссылка $1, которая указывает на то, что нашёл RewriteRule), затем через знак вопроса '?' дописываются обратные ссылки %1 и %3 – которые составляют исходную строку запроса, но уже без mykey.

Следующее решение использует захваченное значение mykey для при создании нового URL, отбрасывая остальную часть исходной строки запроса; на конце добавляется '?':

# Скопировать из строки запроса в PATH_INFO
RewriteCond "%{QUERY_STRING}" "(.*(?:^|&))mykey=([^&]*)&?(.*)&?$"
RewriteRule "(.*)" "$1/products/%2/?" [PT]

Это решение проверяет захваченное значение в последующем условии:

# Захват значения mykey в строке запроса
RewriteCond "%{QUERY_STRING}" "(.*(?:^|&))mykey=([^&]*)&?(.*)&?$"
RewriteCond "%2" !=not-so-secret-value
RewriteRule "(.*)" - [F]

Это решение показывает обратную сторону предыдущих, копируя компоненты пути (возможно, PATH_INFO) из URL-адреса в строку запроса.

Это решение преобразовывает путь до страницы в строку запроса:

# Желаемый URL должен быть /products/kitchen-sink, а скрипт ожидает
# /path?products=kitchen-sink.
RewriteRule "^/?path/([^/]+)/([^/]+)" "/path?$1=$2" [PT]

Распределение URL-адресов по нескольим серверам

Описание:

Популярная техника разграничения нагрузки по нескольким серверам или местам хранения называется шардинг («sharding»). При использовании этого метода внешний сервер будет использовать URL-адрес для последовательной «разбивки» пользователей или объектов по раздельным серверам, выполняющим работу.

Решение:

Отображение поддерживается от пользователей к целевым серверам во внешних файлах карты. Они похожи:

Используется директива RewriteMap с внешней картой доступных целевых серверов. Эта карта выглядит примерно так:

user1 physical_host_of_user1
user2 physical_host_of_user2
: :

Мы помещаем это в файл map.users-to-hosts. Цель состоит в том, чтобы сопоставить;

/u/user1/anypath

с

http://physical_host_of_user1/u/user/anypath

таким образом, каждый путь URL не должен быть действительным для каждого внутреннего хоста. Следующий набор правил делает это для нас с помощью файлов карт, предполагая, что server0 является сервером по умолчанию, который будет использоваться, если у пользователя нет записи на карте:

RewriteEngine on
RewriteMap      users-to-hosts   "txt:/path/to/map.users-to-hosts"
RewriteRule   "^/u/([^/]+)/?(.*)"   "http://${users-to-hosts:$1|server0}/u/$1/$2"

Дополнительную информацию о синтаксисе этой директивы см. в документации RewriteMap.

Генерация контента на лету

Описание:

Мы хотим динамически генерировать контент, но сохраняем его статически после его создания. Это правило будет проверять наличие статического файла, а если его нет, сгенерируйте его. Статические файлы периодически удаляться, если это необходимо (скажем, через cron), и будут восстановлены по требованию.

Решение:

Это делается с помощью следующего набора правил:

# Этот пример работает только в контексте директорий
RewriteCond "%{REQUEST_URI}"   "!-U"
RewriteRule "^(.+)\.html$"          "/regenerate_page.cgi"   [PT,L]

Оператор -U определяет, является ли тестовая строка (в данном случае REQUEST_URI) действительным URL-адресом. Он делает это через подзапрос. В случае сбоя этого подзапроса – то есть запрошенный ресурс не существует – это правило вызывает программу CGI /regenerate_page.cgi, которая генерирует запрошенный ресурс и сохраняет его в каталоге документа, так что в следующий раз запрашивается статическая копия.

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

Балансировка нагрузки

Описание:

Мы хотим случайным образом распределить нагрузку на несколько серверов с помощью mod_rewrite.

Решение:

Мы будем использовать RewriteMap и список серверов для этого.

RewriteEngine on
RewriteMap lb "rnd:/path/to/serverlist.txt"
RewriteRule "^/(.*)" "http://${lb:servers}/$1" [P,L]

Файл serverlist.txt содержит список серверов:

servers one.example.com|two.example.com|three.example.com

Если вы хотите, чтобы один конкретный сервер получал больше нагрузки, чем другие, добавьте его в список несколько раз.

Обсуждение

Apache поставляется с модулем балансировки нагрузки — mod_proxy_balancer – который намного более гибкий и функциональный, чем все, что вы можете комбинировать, используя mod_rewrite.

Структурированные Userdirs

Описание:

Некоторые сайты с тысячами пользователей используют структурированный макет homedir, т. е. Каждый homedir находится в подкаталоге, который начинается (например) с первого символа имени пользователя. Итак, /~larry/anypath это /home/l/larry/public_html/anypath, а /~waldo/anypath это /home/w/waldo/public_html/anypath.

Решение:

Мы используем следующий набор правил для расширения URL тильды в приведенном выше макете.

RewriteEngine on
RewriteRule   "^/~(([a-z])[a-z0-9]+)(.*)"  "/home/$2/$1/public_html$3"

Перенаправление анкеров

Описание:

По умолчанию перенаправление на якорь HTML не работает, потому что mod_rewrite избегает символ #, превращая его в %23. Это, в свою очередь, нарушает перенаправление.

Решение:

Используйте флаг [NE] в RewriteRule. NE означает No Escape – без экранирования.

Обсуждение:

Разумеется, этот метод также будет работать с другими специальными символами, которые mod_rewrite, по умолчанию, кодирует URL-коды.

Зависимая от времени перезапись

Описание:

Мы хотим использовать mod_rewrite для обслуживания различного контента в зависимости от времени суток.

Решение:

Существует много переменных с именем TIME_xxx для условий перезаписи. В сочетании со специальными лексикографическими шаблонами сравнения <СТРОКА, >СТРОКА и =СТРОКА мы можем выполнять зависящие от времени переадресации:

RewriteEngine on
RewriteCond   "%{TIME_HOUR}%{TIME_MIN}" ">0700"
RewriteCond   "%{TIME_HOUR}%{TIME_MIN}" "<1900"
RewriteRule   "^foo\.html$"             "foo.day.html" [L]
RewriteRule   "^foo\.html$"             "foo.night.html"

Это правило делает так, что сервер показывает foo.day.html под URL foo.html с 07:01 по 18:59, а в оставшееся время — содержимое foo.night.html.

mod_cache, промежуточные прокси и браузеры могут показывать сохранённый кэш, т.е. будет показана не та страница, которая установлена для текущего времени обращения. mod_expires может использоваться для управления этим эффектом. Намного лучше, конечно, создавать динамический контент и выдавать разное содержимое в зависимости от времени суток.

Установка переменных среды на основе URL-адресов

Описание:

Допустим теперь мы хотим сохранить какой-то статус при выполнении перезаписи. Например, нужно сделать отметку о том, что выполнилось переписывание, чтобы вы могли позже проверить, является ли этот запрос результатом работы модуля перезаписи. Один из способов сделать это – установить переменную среды.

Решение:

Используйте флаг [E] для установки переменной среды.

RewriteEngine on
RewriteRule   "^/horse/(.*)"   "/pony/$1" [E=rewritten:1]

Позже в вашем наборе правил вы можете проверить эту переменную среды с помощью RewriteCond:

RewriteCond "%{ENV:rewritten}" "=1"

Обратите внимание, что переменные окружения не выдерживают внешнего перенаправления. Вы можете использовать флаг [CO] для установки файла cookie.

Динамические массовые виртуальные хосты с mod_rewrite

mod_rewrite не лучший способ настройки виртуальных хостов. Сначала вы должны рассмотреть альтернативы, прежде чем приступать к mod_rewrite

Виртуальные хосты для произвольных имён хостов

Описание:

Мы хотим автоматически создать виртуальный хост для каждого имени хоста, который резовлиться (указывается) на наш домен, без необходимости создавать новые разделы VirtualHost.

В этом рецепте мы предполагаем, что мы будем использовать имя хоста www.SITE.example.com для каждого пользователя и обслуживать их содержимое из /home/SITE/www.

Решение:

RewriteEngine on
RewriteMap    lowercase int:tolower

RewriteCond   "${lowercase:%{HTTP_HOST}}"   "^www\.([^.]+)\.example\.com$"
RewriteRule   "^(.*)" "/home/%1/www$1"

Обсуждение

Вам нужно будет позаботиться о преобразовании DNS – Apache не обрабатывает преобразование имён. Вам нужно либо создать записи CNAME для каждого имени хоста, либо подстановочную запись DNS. Создание записей DNS выходит за рамки этого документа.

Внутренняя директива tolower RewriteMap используется для обеспечения того, чтобы используемые имена хостов были в нижнем регистре, так что в структуре каталогов не должно быть двусмысленности.

Скобки, используемые в RewriteCond, захватываются в обратные ссылки %1, %2, в то время как скобки, используемые в RewriteRule, захватываются в обратные ссылки $1, $2 и т.д.

Как и многие методы, обсуждаемые в этом документе, mod_rewrite действительно не лучший способ выполнить эту задачу. Вместо него вам следует рассмотреть использование mod_vhost_alias, так как он будет намного более грациозно обрабатывать все, кроме обслуживания статических файлов, таких как любой динамический контент и преобразование псевдонима.

Динамические виртуальные хосты с помощью mod_rewrite

Этот фрагмент из httpd.conf делает то же, что и в первом примере. Первая половина очень похожа на соответствующую часть выше, за исключением некоторых изменений, необходимых для обратной совместимости и для правильной работы mod_rewrite; вторая половина настраивает mod_rewrite для выполнения фактической работы.

Поскольку mod_rewrite работает перед другими модулями трансляции URI (например, mod_alias), модулю mod_rewrite должно быть сказано явно игнорировать любые URL-адреса, которые уже были обработаны этими модулями. И, поскольку эти правила в противном случае обходили бы любые директивы ScriptAlias, мы должны сделать так, чтобы mod_rewrite явно вводил эти сопоставления.

# получаем имя сервера из Host: header
UseCanonicalName Off

# раздельные логи
LogFormat "%{Host}i %h %l %u %t \"%r\" %s %b" vcommon
CustomLog "logs/access_log" vcommon

<Directory "/www/hosts">
    # ExecCGI необходим здесь, поскольку мы не можем принудить
    # CGI выполнение тем образом, как ScriptAlias делает
    Options FollowSymLinks ExecCGI
</Directory>

RewriteEngine On

# ServerName вырезанное из Host: header написанное буквами любого регистра
RewriteMap  lowercase  int:tolower

## сначала работаем с нормальными документами:
# позволяем работать Alias "/icons/" – повторить для других псевдонимов
RewriteCond  "%{REQUEST_URI}"  "!^/icons/"
# позволить выполнить свою работу CGI
RewriteCond  "%{REQUEST_URI}"  "!^/cgi-bin/"
# делаем магию
RewriteRule  "^/(.*)$"  "/www/hosts/${lowercase:%{SERVER_NAME}}/docs/$1"

## и теперь имеем дело с CGI – мы должны форсировать обработчик
RewriteCond  "%{REQUEST_URI}"  "^/cgi-bin/"
RewriteRule  "^/(.*)$"  "/www/hosts/${lowercase:%{SERVER_NAME}}/cgi-bin/$1"  [H=cgi-script]

Использование отдельного файла конфигурации виртуального хоста

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

Файл vhost.map должен выглядеть примерно так:

customer-1.example.com /www/customers/1
customer-2.example.com /www/customers/2
# ...
customer-N.example.com /www/customers/N

httpd.conf должен содержать следующее:

RewriteEngine on

RewriteMap   lowercase  int:tolower

# определяем файл карты
RewriteMap   vhost      "txt:/www/conf/vhost.map"

# обрабатывать псевдонимы, как указано выше
RewriteCond  "%{REQUEST_URI}"               "!^/icons/"
RewriteCond  "%{REQUEST_URI}"               "!^/cgi-bin/"
RewriteCond  "${lowercase:%{SERVER_NAME}}"  "^(.+)$"
# делаем переназначение на основе карты
RewriteCond  "${vhost:%1}"                  "^(/.*)$"
RewriteRule  "^/(.*)$"                      "%1/docs/$1"

RewriteCond  "%{REQUEST_URI}"               "^/cgi-bin/"
RewriteCond  "${lowercase:%{SERVER_NAME}}"  "^(.+)$"
RewriteCond  "${vhost:%1}"                  "^(/.*)$"
RewriteRule  "^/cgi-bin/(.*)$"                      "%1/cgi-bin/$1" [H=cgi-script]

Использование mod_rewrite для проксирования

Проксирование контента с помощью mod_rewrite

Описание:

mod_rewrite предоставляет флаг [P], который позволяет передавать URL-адреса через mod_proxy на другой сервер. Здесь приводятся два примера. В одном примере URL-адрес передается непосредственно на другой сервер и работает так, как если бы это был локальный URL-адрес. В другом примере прокси-сервер пропускает содержимое на внутренний сервер.

Решение:

Чтобы просто сопоставить URL-адрес с другим сервером, мы используем флаг [P], как показано ниже:

RewriteEngine  on
RewriteBase    "/products/"
RewriteRule    "^widget/(.*)$"  "http://product.example.com/widget/$1"  [P]
ProxyPassReverse "/products/widget/" "http://product.example.com/widget/"

Во втором примере мы проксируем запрос только в том случае, если мы не можем найти ресурс локально. Это может быть очень полезно при переходе с одного сервера на другой, и вы не уверены, что все содержимое было перенесено.

RewriteCond "%{REQUEST_FILENAME}"       !-f
RewriteCond "%{REQUEST_FILENAME}"       !-d
RewriteRule "^/(.*)" "http://old.example.com/$1" [P]
ProxyPassReverse "/" "http://old.example.com/"

Обсуждение:

В каждом случае мы добавляем директиву ProxyPassReverse, чтобы гарантировать, что любые перенаправления, отправленные конечным сервером, будут правильно переданы клиенту.

По возможности всегда используйте ProxyPass или ProxyPassMatch вместо mod_rewrite.

Продолжение: «Полное руководство по mod_rewrite (часть 7): Директива RewriteMap».

Рекомендуется Вам:

Добавить комментарий

Ваш e-mail не будет опубликован.