Скрытые особенности mod_rewrite
Кажется, есть приличное количество потоков mod_rewrite
, плавающих в последнее время, с некоторой путаницей относительно того, как некоторые аспекты этого работают. В результате я составил несколько заметок об общей функциональности и, возможно, несколько раздражающих нюансов.
Какие еще функции/общие проблемы вы используете с помощью mod_rewrite
?
Ответы
Ответ 1
Где разместить правила mod_rewrite
mod_rewrite
правила могут быть помещены в файл httpd.conf
или в файл .htaccess
. если у вас есть доступ к httpd.conf
, правила размещения здесь будут иметь преимущество в производительности (поскольку правила обрабатываются один раз, в отличие от каждого момента, когда вызывается файл .htaccess
).
Запись запросов mod_rewrite
Ведение журнала может быть включено из файла httpd.conf
(включая <Virtual Host>
):
# logs can't be enabled from .htaccess
# loglevel > 2 is really spammy!
RewriteLog /path/to/rewrite.log
RewriteLogLevel 2
Общие случаи использования
-
Чтобы перенаправить все запросы в одну точку:
RewriteEngine on
# ignore existing files
RewriteCond %{REQUEST_FILENAME} !-f
# ignore existing directories
RewriteCond %{REQUEST_FILENAME} !-d
# map requests to index.php and append as a query string
RewriteRule ^(.*)$ index.php?query=$1
С Apache 2.2.16 вы также можете использовать FallbackResource
.
-
Обработка перенаправления 301/302:
RewriteEngine on
# 302 Temporary Redirect (302 is the default, but can be specified for clarity)
RewriteRule ^oldpage\.html$ /newpage.html [R=302]
# 301 Permanent Redirect
RewriteRule ^oldpage2\.html$ /newpage.html [R=301]
Примечание: внешние перенаправления неявно 302 перенаправления:
# this rule:
RewriteRule ^somepage\.html$ http://google.com
# is equivalent to:
RewriteRule ^somepage\.html$ http://google.com [R]
# and:
RewriteRule ^somepage\.html$ http://google.com [R=302]
-
Задание SSL
RewriteEngine on
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://example.com/$1 [R,L]
-
Общие флаги:
-
[R]
или [redirect]
- принудительно перенаправить (по умолчанию используется временная переадресация 302)
-
[R=301]
или [redirect=301]
- принудительно перенастроить 301 постоянную ссылку
-
[L]
или [last]
- остановить процесс перезаписи (см. примечание ниже в общих ловушках)
-
[NC]
или [nocase]
- укажите, что соответствие должно быть нечувствительным к регистру.
Использование длинной формы флагов часто более читаемо и поможет другим, которые приходят читать ваш код позже.
Вы можете разделить несколько флагов запятой:
RewriteRule ^olddir(.*)$ /newdir$1 [L,NC]
Общие подводные камни
-
Смешивание mod_alias
переадресаций стиля с помощью mod_rewrite
# Bad
Redirect 302 /somepage.html http://example.com/otherpage.html
RewriteEngine on
RewriteRule ^(.*)$ index.php?query=$1
# Good (use mod_rewrite for both)
RewriteEngine on
# 302 redirect and stop processing
RewriteRule ^somepage.html$ /otherpage.html [R=302,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# handle other redirects
RewriteRule ^(.*)$ index.php?query=$1
Примечание. Вы можете смешивать mod_alias
с mod_rewrite
, но это требует больше работы, чем просто обработка базовых перенаправлений, как указано выше.
-
Контекст влияет на синтаксис
Внутри файлов .htaccess
ведущая косая черта не используется в шаблоне RewriteRule:
# given: GET /directory/file.html
# .htaccess
# result: /newdirectory/file.html
RewriteRule ^directory(.*)$ /newdirectory$1
# .htaccess
# result: no match!
RewriteRule ^/directory(.*)$ /newdirectory$1
# httpd.conf
# result: /newdirectory/file.html
RewriteRule ^/directory(.*)$ /newdirectory$1
# Putting a "?" after the slash will allow it to work in both contexts:
RewriteRule ^/?directory(.*)$ /newdirectory$1
-
[L] не последний! (Иногда)
Флаг [L]
прекращает обработку любых дополнительных правил перезаписи для этого прохождения через набор правил. Однако, если URL-адрес был изменен в этом пропуске, и вы находитесь в контексте .htaccess
или в разделе <Directory>
, ваш измененный запрос снова будет передан обратно через механизм анализа URL. И на следующем проходе он может соответствовать другому правилу на этот раз. Если вы этого не понимаете, часто кажется, что ваш флаг [L]
не имеет эффекта.
# processing does not stop here
RewriteRule ^dirA$ /dirB [L]
# /dirC will be the final result
RewriteRule ^dirB$ /dirC
Наш журнал перезаписи показывает, что правила запускаются дважды, а URL обновляется дважды:
rewrite 'dirA' -> '/dirB'
internal redirect with /dirB [INTERNAL REDIRECT]
rewrite 'dirB' -> '/dirC'
Лучше всего использовать флаг [END]
(см. Документы Apache) вместо флага [L]
, если вы действительно хотят прекратить всю дальнейшую обработку правил (и последующих проходов). Однако флаг [END]
доступен только для Apache v2.3.9 +, поэтому, если у вас есть v2.2 или ниже, вы застряли только с флагом [L]
.
Для более ранних версий вы должны полагаться на операторы RewriteCond
, чтобы предотвратить совпадение правил с последующими проходами механизма синтаксического анализа URL.
# Only process the following RewriteRule if on the first pass
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ...
Или вы должны убедиться, что ваш RewriteRule находится в контексте (т.е. httpd.conf
), который не приведет к повторному анализу вашего запроса.
Ответ 2
если вам нужно "блокировать" внутренние переадресации/перезаписи, происходящие в .htaccess, взгляните на
RewriteCond %{ENV:REDIRECT_STATUS} ^$
поскольку обсуждается здесь.
Ответ 3
Сделка с RewriteBase:
Вам почти всегда нужно установить RewriteBase. Если вы этого не сделаете, apache догадывается, что ваша база - это путь физического диска к вашему каталогу. Итак, начните с этого:
RewriteBase /
Ответ 4
Другие ловушки:
1- Иногда рекомендуется отключить MultiViews
Options -MultiViews
Я не очень хорошо разбираюсь во всех возможностях MultiViews, но я знаю, что он испортил мои правила mod_rewrite, когда они активны, потому что одним из его свойств является попытка "угадать" расширение для файла, который, по его мнению, м ищет.
Я объясню:
Предположим, у вас есть 2 php файла в вашем веб-каталоге, file1.php и file2.php, и вы добавляете эти условия и правила в свой .htaccess:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ file1.php/$1
Вы предполагаете, что все URL-адреса, которые не соответствуют файлу или каталогу, будут захвачены файлом file1.php. Сюрприз! Это правило не соблюдается для URL http://myhost/file2/somepath. Вместо этого вы берете внутри file2.php.
Что происходит, так это то, что MultiViews автоматически угадал, что нужный URL-адрес был http://myhost/file2.php/somepath и с радостью принял вас туда.
Теперь вы не знаете, что произошло, и вы в этот момент ставите под сомнение все, что, по вашему мнению, вы знали о mod_rewrite. Затем вы начинаете играть с правилами, чтобы попытаться понять логику этой новой ситуации, но чем больше вы тестируете, тем меньше она имеет смысл.
Хорошо. Короче, если вы хотите, чтобы mod_rewrite работал таким образом, который приближается к логике, выключение MultiViews является шагом в правильном направлении.
2- включить FollowSymlinks
Options +FollowSymLinks
Это, я не знаю подробностей, но я видел, как это упоминалось много раз, так что просто сделайте это.
Ответ 5
Уравнение может быть выполнено в следующем примере:
RewriteCond %{REQUEST_URI} ^/(server0|server1).*$ [NC]
# %1 is the string that was found above
# %1<>%{HTTP_COOKIE} concatenates first macht with mod_rewrite variable -> "test0<>foo=bar;"
#RewriteCond search for a (.*) in the second part -> \1 is a reference to (.*)
# <> is used as an string separator/indicator, can be replaced by any other character
RewriteCond %1<>%{HTTP_COOKIE} !^(.*)<>.*stickysession=\1.*$ [NC]
RewriteRule ^(.*)$ https://notmatch.domain.com/ [R=301,L]
Динамическая балансировка нагрузки:
Если вы используете mod_proxy для балансировки вашей системы, можно добавить динамический диапазон рабочего сервера.
RewriteCond %{HTTP_COOKIE} ^.*stickysession=route\.server([0-9]{1,2}).*$ [NC]
RewriteRule (.*) https://worker%1.internal.com/$1 [P,L]
Ответ 6
Лучшее понимание флага [L] в порядке. Флаг [L] - последний, вам просто нужно понять, что приведет к тому, что ваш запрос будет перенаправлен через механизм анализа URL снова. Из документов (http://httpd.apache.org/docs/2.2/rewrite/flags.html#flag_l) (внимание мое):
Флаг [L] заставляет mod_rewrite прекратить обработку набора правил. В большинство контекстов, это означает, что если правило соответствует, никаких дальнейших правил будет обработан. Это соответствует последней команде в Perl или команда break в C. Используйте этот флаг, чтобы указать, что текущий правило должно применяться немедленно, не учитывая дальнейшие правила.
Если вы используете RewriteRule в файлах .htaccess или в разделах <Directory>
, важно иметь некоторое представление о как правила обрабатываются. Упрощенная форма этого заключается в том, что один раз правила были обработаны, перезаписанный запрос передан на механизм синтаксического анализа URL-адресов, чтобы сделать то, что он может с ним делать. Возможно, что как обрабатывается обработанный запрос, файл .htaccess или <Directory>
раздел может быть встречен снова, и, таким образом, набор правил может быть запущен снова с самого начала. Чаще всего это произойдет, если один из правила вызывает перенаправление - как внутреннее, так и внешнее - вызывает запросить процесс для начала.
Итак, флаг [L] делает прекратить обработку любых дальнейших правил перезаписи для , которые проходят через набор правил. Однако, если ваше правило, отмеченное знаком [L], изменило запрос, и вы находитесь в контексте .htaccess или в разделе <Directory>
, ваш модифицированный запрос снова будет передан обратно через механизм анализа URL. И на следующем проходе он может соответствовать другому правилу на этот раз. Если вы не понимаете, что произошло, похоже, что ваше первое правило перезаписи с флагом [L] не имело эффекта.
Лучше всего использовать флаг [END] (http://httpd.apache.org/docs/current/rewrite/flags.html#flag_end) вместо флага [L], если вы действительно хотят прекратить всю дальнейшую обработку правил (и последующую репарацию). Тем не менее, флаг [END] доступен только для Apache v2.3.9 +, поэтому, если у вас есть v2.2 или ниже, вы застряли только с флагом [L]. В этом случае вы должны полагаться на операторы RewriteCond, чтобы предотвратить совпадение правил с последующими проходами механизма синтаксического анализа URL. Или вы должны убедиться, что ваш RewriteRule находится в контексте (т.е. Httpd.conf), который не приведет к повторному анализу вашего запроса.
Ответ 7
Еще одна замечательная особенность - перераспределение карт-расширений. Они особенно полезны, если у вас есть массивный массив хостов/переписывающих устройств:
Они похожи на замену ключа:
RewriteMap examplemap txt:/path/to/file/map.txt
Затем вы можете использовать отображение в своих правилах, например:
RewriteRule ^/ex/(.*) ${examplemap:$1}
Более подробную информацию по этой теме можно найти здесь:
http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html#mapfunc
Ответ 8
mod_rewrite может изменять аспекты обработки запросов без изменения URL-адреса, например. настройка переменных среды, настройка файлов cookie и т.д. Это невероятно полезно.
Условно установленная переменная среды:
RewriteCond %{HTTP_COOKIE} myCookie=(a|b) [NC]
RewriteRule .* - [E=MY_ENV_VAR:%b]
Возвратите ответ 503:
Флаг RewriteRule
[R]
может принимать значение не-3xx и возвращать ответ без перенаправления, например. для управляемого времени простоя/обслуживания:
RewriteRule .* - [R=503,L]
вернет ответ 503 (а не перенаправление как таковое).
Кроме того, mod_rewrite может работать как супермощный интерфейс mod_proxy, поэтому вы можете сделать это вместо написания директив ProxyPass
:
RewriteRule ^/(.*)$ balancer://cluster%{REQUEST_URI} [P,QSA,L]
Мнение:
Использование RewriteRule
и RewriteCond
для маршрутизации запросов к различным приложениям или балансировщикам нагрузки на основе практически любого мыслимого аспекта запроса является исключительно мощным. Контролирование запросов на пути к бэкэнд и возможность изменения ответов на обратном пути делает mod_rewrite идеальным местом для централизации всей конфигурации, связанной с маршрутизацией.
Потратьте время, чтобы узнать это, это того стоит!:)