Скрытые возможности mod_rewrite

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

С какими другими функциями/общими проблемами вы столкнулись при использовании mod_rewrite?


person Community    schedule 13.11.2008    source источник
comment
См. также заголовок serverfault.com/questions/214512/   -  person Michael Myers    schedule 13.05.2011


Ответы (8)


Где размещать правила 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

Общие варианты использования

  1. Чтобы направить все запросы в одну точку:

    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.

  2. Обработка 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]
    
  3. Принудительный SSL

    RewriteEngine on
    RewriteCond %{HTTPS} off
    RewriteRule ^(.*)$ https://example.com/$1 [R,L]
    
  4. Общие флаги:

    • [R] or [redirect] - force a redirect (defaults to a 302 temporary redirect)
    • [R=301] или [redirect=301] — принудительная постоянная переадресация 301
    • [L] или [last] - остановить процесс перезаписи (см. примечание ниже в общих ловушках)
    • [NC] или [nocase] — укажите, что соответствие должно быть нечувствительным к регистру.


    Полная форма флагов часто более удобочитаема и поможет другим, кто будет читать ваш код позже.

    Вы можете разделить несколько флагов запятой:

    RewriteRule ^olddir(.*)$ /newdir$1 [L,NC]
    

Распространенные подводные камни

  1. Смешивание стилей переадресации 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, но это требует больше работы, чем просто обработка базовых перенаправлений, как описано выше.

  2. Контекст влияет на синтаксис

    В файлах .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
    
  3. [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 версии 2.3.9+, поэтому, если у вас версия 2.2 или ниже, вы застряли только с флагом [L].

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

    # Only process the following RewriteRule if on the first pass
    RewriteCond %{ENV:REDIRECT_STATUS} ^$
    RewriteRule ...
    

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

person Community    schedule 13.11.2008
comment
Чувак, это лучшая статья в интернете о переписывании модов. Я ненавижу это. Я еретик lighttpd из-за того, как сильно я ненавижу mod_rewrite. - person Kent Fredric; 13.11.2008
comment
Это было САМОЕ полезное руководство, которое я нашел на mod_rewrite до сих пор. Одно только знакомство с RewriteLog помогло решить так много проблем, что то, на что у меня уходили дни, превратилось в несколько минут. (Я имею в виду, что правила были написаны, но я не мог понять, почему они не работали) - person Joe Chin; 27.02.2009
comment
Пост 1-летней давности, но одна из самых полезных вещей, которые я нашел на SO - для меня. - person Erik; 23.01.2010
comment
Если у вас есть доступ только к файлам .htaccess, вам следует игнорировать этот пост, так как советы, относящиеся к .htaccess, часто неверны. например переписать строки substitution также всегда следует опускать косую черту, если вы не знаете, что делаете, и не понимаете, как работает перенаправление абсолютного пути. - person TerryE; 24.01.2012
comment
Флаг [L] означает, что правило является последним в текущей обработке, это не остановит перезапись, потому что это внутренние перенаправления, поэтому ваши dirB применяются к dirC при следующей обработке htaccess. Только RewriteRule ^(.*)$ index.php?query=$1 будет бесконечным циклом внутренних перенаправлений (на практике он завершается после 10 итераций). -1, потому что вы предполагаете, что [L] не последний. Это не завершающий процесс перезаписи, а последний. - person kbec; 15.05.2012
comment
Я считаю, что RewriteCond %{HTTPS} off является предпочтительным способом проверки HTTPS-соединения (в вашем примере принудительного перехода не-ssl-трафика на HTTPS) - person Madbreaks; 01.03.2013
comment
Ух ты, эта проверка %{ENV:REDIRECT_STATUS} просто спасает жизнь. Вы только что спасли то, что осталось от моих волос. - person mbklein; 02.04.2013
comment
Что делает последний. Кажется, никто не может правильно объяснить. Куда он перемещается после обработки L. Перезапускается ли он с самого начала. Идет ли наверх. последний, только если в запросе есть имя файла. Что именно происходит. - person Lpc_dark; 07.05.2013
comment
Было бы неплохо, если бы этот ответ также охватывал случай с MultiViews и mod_rewrite. Я пытаюсь понять это прямо сейчас, и ответ мне не помог, к сожалению. - person Sergei Tachenov; 27.01.2014
comment
Стоит отметить, что директивы RewriteLog и RewriteLogLevel действительны для Apache v2.2. В версии 2.4 теперь используется LogLevel с некоторыми специфическими для модуля уровнями, см. http://httpd.apache.org/docs/current/mod/mod_rewrite.html#logging. Кроме того, упомянутый выше флаг [END] доступен только для версий 2.3.9+ apache. - person JaredC; 14.11.2014
comment
Как уже упоминалось @kbec, флаг [L] является последним, вам просто нужно понять, что заставит анализатор URL-адресов повторно отправить запрос на другой проход через набор правил. Я добавил ответ на этот вопрос, который более подробно описывает, как работает флаг [L]. - person JaredC; 14.11.2014

если вам нужно «заблокировать» внутренние перенаправления / перезаписи в .htaccess, взгляните на

RewriteCond %{ENV:REDIRECT_STATUS} ^$

условие, как описано здесь.

person Community    schedule 22.04.2010
comment
Спасибо, это только что решило мою проблему! - person Matthew; 28.09.2011
comment
Спасибо и за меня, спасатель! - person BenMorel; 06.08.2013
comment
Это действительно спасение жизни! Люди должны быть более осведомлены об этом. На самом деле, я собираюсь предлагать это на каждый вопрос о .* с флагом [L], который я читал до того, как попал сюда. - person Qwerty; 25.05.2014
comment
Я видел несколько модификаций этого 200, !=200, ^., ^$. По-видимому, переменная устанавливается на 200 для перенаправления, но и другие страницы (ошибки и прочее) устанавливают для нее какое-то значение. Теперь это означает, что вы либо проверяете, является ли это is empty, is not empty, is 200 или is not 200, в зависимости от того, что вам нужно. - person Qwerty; 25.05.2014

Сделка с RewriteBase:

Вам почти всегда нужно установить RewriteBase. Если вы этого не сделаете, apache догадается, что ваша база — это путь физического диска к вашему каталогу. Итак, начните с этого:

RewriteBase /
person Community    schedule 27.08.2009
comment
Ах. Это полностью устранило проблему, с которой я столкнулся. Спасибо за это! - person Tom Savage; 18.02.2010
comment
Любой способ сказать RewriteBase . или что-то еще, чтобы указать, что он должен сохранить тот же URL-адрес, просто изменив то, что вы указали? - person Jay K; 09.09.2011
comment
Спасибо, это была бесценная информация. :) - person AturSams; 15.10.2011
comment
Вам нужно установить RewriteBase только в том случае, если вы используете подстановку относительного пути в директиве RewriteRule. Лучше избегать использования относительных путей. - person MrWhite; 27.01.2015
comment
Я не согласен с этим ответом. В нашей команде разработчиков мы вообще избегаем RewriteBase, так как почти все разработчики неправильно понимают, что он делает. Как сказал @w3d, он вам нужен только в том случае, если вы хотите сохранить символы и хотите применить одну и ту же базу ко всем вашим RewriteRules в одном файле. Ваш код, вероятно, будет более понятным для других, если вы избегаете его. - person Simon East; 10.12.2015
comment
Вам никогда не понадобится RewriteBase, пока вы используете абсолютные замены. Прочтите подробное руководство по адресу RewriteBase. - person Olaf Dietsche; 08.01.2017

Другие подводные камни:

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 

Я не знаю подробностей об этом, но я много раз видел его упоминания, так что просто сделайте это.

person Community    schedule 19.08.2009
comment
Спасибо :) Я заметил неожиданные сюрпризы, такие как /log/activity, превращающиеся в /log.txt/activity .. Спасибо за подсказку :) .. жаль, что компьютеры никогда не развлекают неожиданные вещи, такие как случайное соблазнение всех ваших коллег-женщин на Facebook :) - person AturSams; 16.10.2011
comment
+FollowSymLinks упоминается в документации как обязательное условие для работы mod_rewrite из соображений безопасности. - person Joey; 13.11.2011
comment
Два утверждения здесь очень беспокоят меня: «Я не очень хорошо разбираюсь во всех возможностях MultiViews, но я знаю, что это портит мои правила mod_rewrite, когда они активны» и одно «Это одно, я действительно не знаю деталей , но я видел, как это упоминалось много раз, так что просто сделайте это». Я бы хотел, чтобы такие люди, как вы, не писали ответы на SO о вещах, в которых вы не уверены. - person TheCarver; 31.08.2013
comment
@PaparazzoKid: я думаю, вы принимаете SO за энциклопедию. Это сообщество людей, которые собираются вместе, чтобы лучше понять технологию, с которой они работают. В отличие от А.В. Уайт и Джоуи перед вами, ваш комментарий почти не имеет ценности. MV и FSL — это два из многих вариантов Apache. Мой ответ о подводных камнях при работе конкретно с mod_rw, отдельным модулем, который конфликтует с одними опциями и работает с другими. Я объяснил, как MV влияет на mod_rw, и упомянул, что +FSL является популярной рекомендацией. Джоуи подтвердил, что это действительно обязательно. Что вы приносите к столу? - person Michael Ekoka; 19.10.2013
comment
Спасибо. Я только что потратил большую часть часа на то, чтобы унаследованный сайт работал и пытался отладить правила перезаписи, только чтобы обнаружить, что MultiViews переопределяет все это. - person Andrew McCombe; 02.05.2014
comment
Просто чтобы добавить к примечанию FollowSymLinks... да, как утверждает Джоуи, это необходимо для включения перезаписей в контексте каждого каталога (Ссылка в документации). И Options +FollowSymLinks (обратите внимание на +) — это безопасный способ сделать это, если он еще не включен, при условии, что AllowOverride не блокирует это в конфигурации сервера (что является одним из способов заблокировать mod_rewrite в файлах .htaccess). - person MrWhite; 27.01.2015

Уравнение можно составить на следующем примере:

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]
person Community    schedule 19.01.2010

Необходимо лучше понять флаг [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), который не приведет к повторному анализу вашего запроса.

person Community    schedule 14.11.2014

Еще одна замечательная функция — переписать карты-расширения. Они особенно полезны, если вам нужно обработать огромное количество хостов/перезаписей:

Они похожи на замену ключ-значение:

RewriteMap examplemap txt:/path/to/file/map.txt

Затем вы можете использовать сопоставление в своих правилах, например:

RewriteRule ^/ex/(.*) ${examplemap:$1}

Более подробную информацию по этой теме можно найти здесь:

http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html#mapfunc

person Community    schedule 19.08.2009
comment
Игнорируйте эту функцию, если вы используете перезапись на основе .htaccess. В данном контексте это не работает. - person TerryE; 24.01.2012
comment
Директива RewriteMap должна использоваться в контексте сервера (httpd.conf), но после определения там вы можете использовать карту через RewriteRule в файле .htaccess. - person JaredC; 14.11.2014

mod_rewrite может изменять аспекты обработки запросов без изменения URL-адреса, например. установка переменных среды, установка файлов cookie и т. д. Это невероятно полезно.

Условно установите переменную среды:

RewriteCond %{HTTP_COOKIE} myCookie=(a|b) [NC]
RewriteRule .* - [E=MY_ENV_VAR:%b]

Вернуть ответ 503: флаг [R] RewriteRule может принимать значение, отличное от 3xx, и возвращать ответ без перенаправления, например. для управляемого простоя/обслуживания:

RewriteRule .* - [R=503,L]

вернет ответ 503 (не перенаправление как таковое).

Кроме того, mod_rewrite может действовать как сверхмощный интерфейс для mod_proxy, так что вы можете сделать это вместо того, чтобы писать директивы ProxyPass:

RewriteRule ^/(.*)$ balancer://cluster%{REQUEST_URI} [P,QSA,L]

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

Потратьте время, чтобы изучить его, это того стоит! :)

person Community    schedule 12.02.2014