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

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