This page has been robot translated, sorry for typos if any. Original content here.

Як насправді працює mod_rewrite. Посібник для продовжують

Как на самом деле работает mod_rewrite. Пособие для продолжающих

Ця стаття виросла з ідеї просунутого навчання наших співробітників технічної підтримки роботи з mod_rewrite. Практика показала, що після вивчення наявних у великій кількості підручників російською мовою САППОРТ добре дається рішення шаблонних завдань, але ось самостійне складання правил відбувається методом проб і великої кількості помилок. Проблема полягає в тому, що для доброго розуміння роботи mod_rewrite потрібно вивчення оригінальної англомовної документації, після чого - або додаткові роз'яснення, або годинник експериментів з RewriteLog.

У статті викладено механізм роботи mod_rewrite. Розуміння принципів його роботи дозволяє чітко усвідомлювати дію кожної директиви і чітко уявляти собі, що відбувається в той чи інший момент всередині mod_rewrite при обробці директив.

Я припускаю, що читач вже знайомий з тим, що таке mod_rewrite, і не буду описувати його основи, які легко знайти в інтернеті. Також потрібно відзначити, що в статті висвітлюється робота mod_rewrite при використанні його директив у файлі .htaccess. Відмінності при роботі в контексті викладені в кінці статті.

Отже, ви вивчили mod_rewrite, склали кілька RewriteRule і встигли зіткнутися з нескінченними перенаправлення, з випадком, коли правило чомусь не ловить ваш запит, а також з непередбачуваною роботою групи правил, коли подальше правило несподівано змінює запит, ретельно підготовлений правилами попередніми.

З чим працює RewriteRule

Першому RewriteRule передається шлях від того місця, де знаходиться .htaccess, до запитаного файлу. Цей рядок ніколи не починається зі "/". Наступним RewriteRule передається результат попередніх перетворень.

Щоб досконально зрозуміти, як працює RewriteRule, необхідно спочатку визначити, з чим він працює. Розглянемо, як Apache отримує рядок, яка спочатку передається на обробку RewriteRule в .htaccess.

Коли тільки починаєш працювати з mod_rewrite, логічно припускаєш, що він працює з посиланнями. Однак у випадку з використанням mod_rewrite в .htaccess це не так. Насправді в RewriteRule передається не посилання, а шлях до запитаного файлу.

Через внутрішню архітектури Apache в той момент, коли в дію вступає .htaccess, mod_rewrite може оперувати тільки з шляхом до файлу, який повинен бути оброблений. Це пов'язано з тим, що до передачі в mod_rewrite запит вже могли змінити інші модулі (наприклад, mod_alias), і підсумковий шлях до файлу на сайті вже може не збігатися з вихідною посиланням. Якби mod_rewrite працював з вихідної посиланням, він би порушував дію модулів, які змінили запит до нього.

Тому в mod_rewrite передається абсолютний шлях до файлу, який повинен бути оброблений. Також mod_rewrite знає шлях до .htaccess, в якому розміщено інформацію про правила RewriteRule. Щоб зробити з шляху до файлу щось схоже на посилання, з якої планує працювати розробник сайту, mod_rewrite відрізає від абсолютного шляху частина до файлу .htaccess.

Так ось, саме цей шлях, від якого відрізаний шлях до .htaccess, передається в перший RewriteRule. наприклад:

  • Запит: http://example.com/templates/silver/images/logo.gif
  • DocumentRoot: /var/www/example.com
  • Шлях до файлу: /var/www/example.com/templates/silver/images/logo.gif
  • .htaccess знаходиться в: /var/www/example.com/templates/.htaccess
  • У перший RewriteRule буде передано: silver / images / logo.gif
  • Зверніть увагу: «templates /» теж відрізалось.
Как на самом деле работает mod_rewrite. Пособие для продолжающих

Шлях до .htaccess відрізається разом зі слешем. З цього є наслідок: рядок, яка спочатку передається на обробку RewriteRule ніколи не починається зі "/".

Важливо запам'ятати, що не чинить RewriteRule. Вона не виконує жодних ім'я сайту, аргументи, які передані в скрипт, та й посилання обробляє не всю, якщо .htaccess розміщений не в корені сайту. Всім цим займається RewriteCond, якого коротко торкнемося трохи пізніше. Отже:

# работать не будет - правило начинается со /
RewriteRule ^/index.php$ /my-index.php

# работать не будет - название сайта не анализируется RewriteRule
RewriteRule ^example.com/.* http://www.example.com

# работать не будет - аргументы ссылки не попадают в RewriteRule
RewriteRule index.php\?newspage=([0-9]+) news.php?page=$1
# работать не будет - правило начинается со /
RewriteRule ^/index.php$ /my-index.php

# работать не будет - название сайта не анализируется RewriteRule
RewriteRule ^example.com/.* http://www.example.com

# работать не будет - аргументы ссылки не попадают в RewriteRule
RewriteRule index.php\?newspage=([0-9]+) news.php?page=$1
# Будет работать только если .htaccess находится там же, где находится папка templates,
# например, в корне сайта. То есть, если .htaccess находится в templates/.htaccess , правило
# работать НЕ БУДЕТ, потому что mod_rewrite отрежет путь до .htaccess и на вход RewriteRule
# строка попадет уже без "templates/"
RewriteRule ^templates/common/yandex-money.gif$ templates/shared/yad.gif
# Будет работать только если .htaccess находится там же, где находится папка templates,
# например, в корне сайта. То есть, если .htaccess находится в templates/.htaccess , правило
# работать НЕ БУДЕТ, потому что mod_rewrite отрежет путь до .htaccess и на вход RewriteRule
# строка попадет уже без "templates/"
RewriteRule ^templates/common/yandex-money.gif$ templates/shared/yad.gif

На початку використання mod_rewrite я рекомендую працювати з ним тільки в .htaccess в корені сайту. Це кілька спростить контроль за його роботою.

З чим працює RewriteRule, ми розібралися. Тепер подивимося, як він працює.

Як працює RewriteRule

RewriteRule просто перетворює рядок відповідно до регулярними виразами, і все. RewriteRule працює з рядком, а не з посиланням або шляхом до файлу.

Як ми з'ясували вище, на вхід RewriteRule потрапляє шлях від .htaccess до запитаного файлу. Найзручніше тепер абстрагуватися від шляхів і посилань і розглядати те, з чим працює RewriteRule, як звичайний рядок. Цей рядок передається від RewriteRule до RewriteRule, видозмінюючись, якщо якийсь з RewriteRule спрацювало.

У загальному вигляді, якщо виключити труднощі з використанням прапорів (деякі з яких ми розглянемо нижче) і складності зі складанням регулярних виразів (яких ми майже не будемо торкатися в цій статті), RewriteRule працює ДУЖЕ просто.

  1. Взяли рядок.
  2. Порівняли з регулярним виразом в першому аргументі.
  3. Якщо є збіг - замінили всю рядок на значення другого аргументу.
  4. Передали рядок наступного RewriteRule.

Ось, загалом, і все. Щоб наочно проілюструвати, що RewriteRule працює саме з рядком, розглянемо наступний фантастичний приклад:

# Запрос: http://mysite.com/info.html
# В первый RewriteRule попадет "info.html"

# Преобразовываем запрос в произвольную строку.
RewriteRule ^info.html$ "I saw a turtle in the hole. And it was dancing rock-n-roll. And it was smiling. All in all, it was a very funny doll."

# "info.html" -> "I saw a turtle..."

# Заменяем эту строку на внешнюю ссылку.
RewriteRule turtle https://example.com/information/index.html

# "I saw a turtle..." -> "https://example.com/information/index.html"

# Заменяем имя сайта!
RewriteRule ^(.*)example.com(.*)$ $1example.org$2

# "https://example.com/information/index.html" -> "https://example.org/information/index.html"

# Заменяем протокол!
RewriteRule ^https :( .*)$ ftp:$1

# "https://example.org/information/index.html" -> "ftp://example.org/information/index.html"

# Заменяем конечную ссылку.
RewriteRule ^(.*)/index.html$ $1/main.php

# "ftp://example.org/information/index.html" -> "ftp://example.org/information/main.php"

Як бачите, RewriteRule все одно, з чим працювати - вона просто перетворює рядок у відповідності із заданими їй аргументами. Якщо хочете, можете в рядку зберігати будь-які масиви даних, при бажанні, наполегливості і хорошому знанні регулярних виразів можете хоч хрестики-нулики на RewriteRule написати.

Тут потрібно зробити зауваження: хоч RewriteRule і працює з чистою рядком, вона все-таки орієнтована на роботу з посиланнями. Тому вона буде по-особливому реагувати на рядки, що починаються на «https: //» або аналоги (запам'ятає, що ми хотіли зробити зовнішній редирект) і на символ "?" (Вважатиме такі символи аргументами, які потрібно буде підставити до запиту). Однак зараз нас це не цікавить - важливо зрозуміти, що в RewriteRule немає ніякої магії - вона просто бере рядок і змінює її так, як ви їй сказали. Зовнішні редіректи і аргументи ми розглянемо пізніше в статті, там теж є, про що поговорити.

Після того як всі перетворення проведені і виконано останнім RewriteRule, вступає в силу RewriteBase.

Для чого потрібен RewriteBase

Якщо отриманий після перетворень запит є відносним і відрізняється від вихідного, RewriteBase додасть себе до нього зліва. Потрібно обов'язково вказувати RewriteBase в .htaccess. Його значення - шлях від кореня сайту до .htaccess. RewriteBase виконується тільки після всіх RewriteRule, а не між ними.

Ми вже говорили вище про те, що в mod_rewrite, що працює в .htaccess, потрапляє абсолютний шлях до запитаного файлу. Щоб передати його в RewriteRule, mod_rewrite відрізає шлях до .htaccess. Потім правила RewriteRule одне за одним послідовно змінюють запит. І ось після того, як запит змінений, Apache повинен відновити абсолютний шлях до файлу, який він повинен в результаті обробити. RewriteBase фактично є хаком, який допомагає відновити вихідний шлях до файлу.

RewriteBase виконується після всіх перетворень. Це означає, що він не буде змінювати запит між RewriteRule, а вступить в силу тільки коли все RewriteRule відпрацюють.

Після всіх перетворень RewriteBase дивиться, відносний вийшов в результаті шлях або абсолютний. В контексті Apache мається на увазі відносний або абсолютний шлях, відраховуючи від кореня сайту:

  • images / logo.gif - відносний.
  • /images/logo.gif - абсолютний (на початку слеш).
  • http://example.com/images/logo.gif - самий абсолютний зі всіх.

Якщо шлях абсолютний, RewriteBase нічого не робить. А якщо відносний - RewriteBase дописує себе зліва. Це працює як для внутрішніх, так і для зовнішніх редиректів:

# .htaccess находится в /images/
# RewriteBase указан /images/
RewriteBase /images/

# Запрос http://example.com/images/logo.gif
# На вход RewriteRule попадает "logo.gif"
RewriteRule ^logo.gif$ logo-orange.gif
# После RewriteRule: "logo.gif" -> "logo-orange.gif"
# После RewriteBase: "logo-orange.gif" -> "/images/logo-orange.gif"

# Запрос http://example.com/images/header.png
# На вход RewriteRule попадает "header.png"
RewriteRule ^header.png$ /templates/rebranding/header.png
# После RewriteRule: "header.png" -> "/templates/rebranding/header.png"
# После RewriteBase: ничего не меняется, так итоговый результат преобразований начинается со "/'.

# Запрос http://example.com/images/director.tiff
# На вход RewriteRule попадает "director.tiff"
# Используем внешний относительный редирект
RewriteRule ^director.tiff$ staff/manager/director.tiff [R=301]
# После RewriteRule: "director.tiff" -> "staff/manager/director.tiff"
# + mod_rewrite запомнил, что будет внешний редирект
# После RewriteBase: "staff/manager/director.tiff" -> "/images/staff/manager/director.tiff"
# mod_rewrite вспомнил про внешний редирект:
# "/images/staff/manager/director.tiff" -> http://example.com/images/staff/manager/director.tiff

Зазвичай після деякого знайомства з mod_rewrite складається наступна звичка: 1) в кожен .htaccess додавати «RewriteBase /», 2) всі перенаправлення починати зі слеша: «RewriteRule news.php /index.php?act=news». Це допомагає позбутися від артефактів роботи RewriteBase, але так робити неправильно. Тепер, коли ми знаємо, що робить RewriteBase, можна сформулювати такі коректні правила:

  1. RewriteBase повинен збігатися з шляхом від кореня сайту до .htaccess.
  2. Починати перенаправлення зі "/" потрібно тільки тоді, коли необхідно вказати абсолютний шлях від кореня сайту до файлу.
Как на самом деле работает mod_rewrite. Пособие для продолжающих

Що буде, якщо не вказати RewriteBase? За замовчуванням Apache робить його рівним абсолютного шляху на файлової системи до .htaccess (наприклад, /var/www/example.com/templates/). Некоректність такого припущення Apache проявляється на зовнішніх відносних редирект:

# Запрос http://example.com/index.php
# DocumentRoot: /var/www/example.com/
# .htaccess находится в корне сайта, и в нем НЕ УКАЗАН RewriteBase.
# Поэтому по умолчанию RewriteBase равен абсолютному пути до .htaccess: /var/www/example.com/

# На входе RewriteRule - "index.php"
RewriteRule ^index.php main.php [R]
# На выходе: "index.php" -> "main.php"
# mod_rewrite запомнил, что нужен внешний редирект

# Закончились RewriteRule
# mod_rewrite все равно выполняет RewriteBase, так как у него есть значение по умолчанию.
# Получается: "main.php" -> "/var/www/example.com/main.php"

# Здесь mod_rewrite вспоминает, что был внешний редирект:
# "/var/www/example.com/main.php" -> http://example.com/var/www/example.com/main.php

# Получилось совсем не то, что имели в виду.

Отже, запит пройшов через все RewriteRule, після чого до нього, в разі необхідності, додався RewriteBase. Чи повинен тепер Apache віддати файл, на який показує результуючий шлях? Ні. Тепер вийшов запит буде оброблятися ще раз.

Як працює mod_rewrite. Прапор [L]

mod_rewrite запускає обробку запиту знову і знову, до тих пір, поки він не перестане змінюватися. І прапор [L] не може це зупинити.

При складанні більш-менш складних конфігурацій mod_rewrite важливо розуміти, що зміна запиту не закінчується на останньому RewriteRule. Після того, як спрацювало останнє правило RewriteRule і був доданий RewriteBase, mod_rewrite дивиться, змінився запит чи ні. Якщо запит змінився, його обробка починається заново з початку .htaccess.

Apache надходить так, тому що в процесі зміни запиту він міг бути перенаправлений в іншу директорію. У ній може бути власний .htaccess, який не брав участі в попередній обробці запиту. У цьому ж новому .htaccess можуть бути правила, які впливають на обробку запиту - як правила mod_rewrite, так і правила інших модулів. Щоб коректно обробити цю ситуацію, Apache повинен запустити весь цикл обробки заново.

- Стривайте, але ж є прапор [L], який зупиняє обробку запиту mod_rewrite'ом!

Не зовсім так. Прапор [L] зупиняє поточну ітерацію обробки запиту. Однак якщо запит був змінений тими RewriteRule, які все-таки встигли відпрацювати, Apache запустить цикл обробки запиту заново з першого RewriteRule.

# Запрос: http://example.com/a.html

RewriteBase /

RewriteRule ^a.html$ b.html [L]
RewriteRule ^b.html$ a.html [L]

Приклад вище призведе до нескінченного циклу перенаправлень і до «Internal Server Error» в результаті. У цьому прикладі нескінченний цикл очевидний, проте в більш складних конфігураціях може знадобитися покопатися в правилах, щоб визначити, які запити зациклюються між собою.

Щоб уникнути подібних ситуацій, рекомендується використовувати прапор [L] тільки при необхідності. Необхідність може бути двох типів:

  1. Коли використовується зовнішній редирект - [L, R = 301] або [L, R = 302]. У разі зовнішнього редиректу подальша обробка запиту небажана (див. Нижче про прапор [R]), і її краще зупинить
  2. Коли в .htaccess є зациклення, від якого не позбутися, і обробку запиту mod_rewrite'ом потрібно примусово припинити. У цьому випадку використовується спеціальна конструкція - см. В кінці статті поради на цю тему.

А ось наведений нижче приклад зациклюватися не буде. Спробуйте визначити, чому, і який в результаті файл буде відданий Apache'м.

# Запрос: http://example.com/a.html
# Начало .htaccess

RewriteBase /
RewriteRule ^a.html$ b.html
RewriteRule ^b.html$ a.html

# Конец .htaccess

Як працює mod_rewrite. Прапор [R]

Прапор [R] не зупиняє обробку запиту, якщо будете повертати зовнішній редирект. Замість цього він запам'ятовує необхідність зовнішнього редиректу, і обробка запиту триває наступними RewriteRule. Рекомендується завжди використовувати з прапором [L].

Прапор [R] повідомляє Apache, що потрібно виконати не внутрішній, а зовнішній редирект. Чим відрізняється зовнішній редирект від внутрішнього? Внутрішній редирект просто змінює шлях до файлу, який буде відданий користувачеві, при цьому користувач вважає, що отримує той файл, який він спочатку запросив. При зовнішньому ж редирект Apache замість вмісту файлу повертає користувачеві статус відповіді 301 або 302 та повідомляє посилання, по якій браузер повинен звернутися для отримання файлу.

Здавалося б, при обробці прапора [R] Apache повинен відразу припинити обробку RewriteRule і повернути користувачеві зовнішній редирект. Однак давайте згадаємо фантастичний приклад з розділу «Як працює RewriteRule». У ньому ми спочатку вказали прапор [R], позначивши необхідність зовнішнього редиректу, після чого продовжили змінювати посилання наступними RewriteRule.

Саме так і працює Apache при вказівці зовнішнього редиректу. Він просто «позначає» собі, що після виконання всіх правил необхідно повернути статус 302 (за замовчуванням), але при цьому продовжує виконання всіх RewriteRule далі за списком. Ми можемо і далі змінювати запит як нам потрібно, єдине, що не вийде - зробити редирект назад внутрішнім.

Проте, навряд чи ви хочете після віддачі зовнішнього редиректу будь-яким чином змінювати його. Тому рекомендується при вживанні прапора [R] вказувати його спільно з [L]:

# BlackJack переехал на красивое имя
RewriteRule ^bj/(.*) blackjack/$1 [R=301,L]

# Можно использовать просто внешнюю ссылку
RewriteRule ^bj/(.*) http://blackjack.example.com/$1 [L]

Замість використання прапора [R] можна вказувати просто зовнішнє посилання. В цьому випадку Apache сам здогадається, що необхідно зробити зовнішній редирект. Тут, як і з у випадку з явним зазначенням прапора [R], рекомендується використовувати прапор [L].

  • Якщо зовнішній редирект веде на той же сайт, краще використовувати прапор [R] без вказівки повної посилання (іншими словами, використовувати відносний зовнішній редирект). Це зробить правило незалежним від імені сайту.
  • Якщо ж зовнішній редирект веде на інший сайт, інакше, як вказавши повну зовнішнє посилання, це зробити не вийде.

Як працює mod_rewrite. Вказівка ​​параметрів запиту та прапор [QSA]

Зміна параметрів запиту в RewriteRule не змінює рядок, з якої працює наступний RewriteRule. Однак при зміні параметрів змінюється змінна% {QUERY_STRING}, з якої може працювати RewriteCond.

Використовувана термінологія: «параметри» - параметри запиту, «аргументи» - аргументи RewriteRule.

За допомогою RewriteRule можна змінювати не тільки шлях до файлу, який буде оброблятися, але і параметри запиту GET, які будуть йому передаватися. Це часто використовується для передачі обробки ЧПУ в загальний скрипт-обробник, наприклад:

RewriteBase /

# Запрос: http://example.com/news/2010/07/12/grand-opening.html
# На входе: "news/2010/07/12/grand-opening.html"
RewriteRule ^news/(.*)$ index.php?act=news&what=$1
# После RewriteRule: "news/2010/07/12/grand-opening.html" -> "index.php"
# %{QUERY_STRING}: "" -> "act=news&what=2010/07/12/grand-opening.html"

У момент, коли правило RewriteRule зустрічає знак питання в другому аргументі, воно розуміє, що відбувається зміна параметрів в запиті. В результаті відбувається наступне:

  1. RewriteRule замінює рядок, з якої воно працює, на частину другого аргументу до знаку. Зверніть увагу, що нові параметри запиту не потрапляють в рядок, з якої будуть працювати наступні правила RewriteRule.
  2. Частина другого аргументу після знаку питання потрапляє в змінну% {QUERY_STRING}. Якщо було вказано прапор [QSA], параметри запиту будуть додані в початок% {QUERY_STRING}. Якщо прапор зазначений не був,% {QUERY_STRING} повністю заміниться параметрами запиту з RewriteRule.

Ще пара прикладів:

RewriteBase /

# Запрос: http://example.com/news/2010/?page=2
# На входе RewriteRule: "news/2010/"
RewriteRule ^news/(.*)$ index.php?act=news&what=$1
# После преобразования: "news/2010/" -> "index.php"
# Значение %{QUERY_STRING}: "page=2" -> "act=news&what=2010/"

Швидше за все, правило вище не працює належним чином, так як втрачається аргумент page. Виправимо це:

RewriteBase /

# Запрос: http://example.com/news/2010/?page=2
# На входе RewriteRule: "news/2010/"
RewriteRule ^news/(.*)$ index.php?act=news&what=$1 [QSA]
# После преобразования: "news/2010/" -> "index.php"
# Значение %{QUERY_STRING}: "page=2" -> "act=news&what=2010/&page=2"

Ми додали тільки прапор [QSA], і правило стало працювати коректно.

ажно розуміти, що зміна параметрів запиту змінює% {QUERY_STRING}, який може використовуватися в подальшому в RewriteCond. Це потрібно враховувати при складанні наступних правил, які перевіряють аргументи.

- Звичайно, змінюється, адже запит йде на повторну обробку Apache'м!

Ні,% {QUERY_STRING} змінюється відразу ж. Доказ наводити не буду - про параметри і так вже написано більше, ніж цікаво читати :)

Що ж робити, щоб перевірити в RewriteCond саме ті параметри запиту, які передав користувач, а не модифіковані RewriteRule'амі? Дивіться поради в кінці статті.

RewriteCond і продуктивність

Спочатку перевіряється збіг запиту з RewriteRule, а вже потім - додаткові умови RewriteCond.

Пару слів варто сказати про те, в якому порядку mod_rewrite виконує директиви. Так як в .htaccess спочатку йдуть RewriteCond, а потім RewriteRule, здається, що mod_rewrite спочатку перевіряє всі умови, а потім приступає до виконання RewriteRule.

Насправді все відбувається навпаки. Спочатку mod_rewrite перевіряє, чи підходить поточне значення запиту під регулярний вираз RewriteRule, а вже потім буде перевіряти всі умови, перераховані в RewriteCond.

Так що якщо у вас в RewriteRule регулярний вираз на дві сторінки і ви, замислившись про продуктивність, вирішили обмежити виконання цього правила додатковими RewriteCond, знайте - нічого не вийде. В цьому випадку краще використовувати прапори RewriteRule [C] або [S], щоб пропустити складніше правило, якщо більш прості перевірки не спрацювали.

Змінні і прапори RewriteCond, інші прапори RewriteRule та інше

Читайте документацію.

Ми познайомилися з принципами роботи RewriteRule, RewriteBase, прапорів [L], [R] і [QSA], а також розібрали механізм обробки запитів всередині mod_rewrite. З незачепленою залишилися: інші прапори RewriteRule, директиви RewriteCond і RewriteMap.

На щастя, ці директиви і прапори не таять в собі будь-яких загадок і працюють саме так, як описано в більшості підручників. Для їх розуміння досить почитати офіційну документацію. В першу чергу рекомендую вивчити список змінних, які можна перевіряти в RewriteCond -% {QUERY_STING},% {THE_REQUEST},% {REMOTE_ADDR},% {HTTP_HOST},% {HTTP: header} і т. Д.)

Різниця в роботі mod_rewrite в контексті .htaccess і в контексті VirtualHost

В контексті mod_rewrite працює з точністю до навпаки.

Як я говорив на початку статті, все описане вище стосується застосування mod_rewrite в контексті .htaccess. Якщо ж mod_rewrite використовується в , Він буде працювати по-іншому:

  • В в RewriteRule потрапляє весь шлях запиту, починаючи від першого слеша, закінчуючи початком параметрів GET: «http://example.com/some/news/category/post.html?comments_page=3» -> "/ news / category / post. html ". Цей рядок завжди починається зі /.
  • Другий аргумент RewriteRule також необхідно починати з /, інакше буде «Bad Request».
  • RewriteBase не має сенсу.
  • Прохід правил відбувається тільки один раз. Прапор [L] дійсно закінчує обробку всіх правил, описаних в , Без будь-яких подальших ітерацій.

Поради та рішення

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

Складання регулярних виразів

Намагайтеся складати регулярні вирази так, щоб вони найбільш вузько визначали саме ті запити, які ви хочете модифікувати - щоб правила RewriteRule випадково не спрацювали для іншого запиту. наприклад:

# Начинайте все регулярные выражения с "^" (признак начала строки)
# и заканчивайте "$" (признак конца строки):
RewriteRule ^news.php$ index.php
# Даже если в этом нет необходимости - для универсальности и лучшего понимания конфигурации:
RewriteRule ^news/(.*)$ index.php

# Если под маску должны попадать только цифры - укажите это явно.
# Если какие-то цифры постоянны, укажите их явно.
# Если в оставшейся части запроса не могут присутствовать слеши, ограничьте их присутствие.
# Не забывайте экранировать "." (точки).
# Следующее правило нацелено на запросы вида http://example.com/news/2009/07/28/b-effect.html
RewriteRule ^news/20[0-9]{2}/[0-9]{2}/[0-9]{2}/[^/]+\.html index.php

Втім, про регулярні вирази ви можете почитати і у нас на сайті:

Зміна зовнішніх редиректів

Незважаючи на те, що mod_rewrite дозволяє змінювати за допомогою RewriteRule навіть зовнішні редіректи, аж до протоколу, я вкрай не рекомендую робити це. У статті приклад зі зміною зовнішніх редиректів використовується тільки щоб відв'язатися від таких понять як «посилання» і «файли» і більш явно показати, що RewriteRule працює з простий рядком.

Не думаю, що розробники mod_rewrite припускали, що хтось буде так робити, тому можливі всілякі артефакти. Не робіть так, будь ласка.

Як зупинити нескінченний цикл

Іноді логіка перенаправлень на сайті така, що без спеціальних дій mod_rewrite сприймає їх як нескінченний цикл перенаправлень. Візьмемо такий приклад.

На сайті була сторінка /info.html. Спеціаліст по SEO вирішив, що пошукові системи будуть краще індексувати цю сторінку, якщо вона буде називатися /information.html і попросив зробити зовнішній редирект з info.html на information.html. Однак розробник сайту з якихось своїх міркувань не може просто перейменувати info.html в information.html і зробити редирект - йому потрібно, щоб дані обов'язково віддавалися безпосередньо з файлу info.html. Він пише наступне правило:

# сделать внешний редирект
RewriteRule ^info.html information.html [R,L]
# но по запросу /information.html все равно отдать info.html
RewriteRule ^information.html info.html

... і стикається з нескінченним циклом. Кожен запит /information.html отримує зовнішній редирект знову на /information.html.

Вирішити цю проблему можна як мінімум двома способами. На Хабре був уже описаний один з них - потрібно встановити змінну оточення і на підставі її значення припиняти перенаправлення. Код буде виглядати наступним чином:

RewriteCond %{ENV:REDIRECT_FINISH} !^$
RewriteRule ^ - [L]

RewriteRule ^info.html$ information.html [R,L]
RewriteRule ^information.html$ info.html [E=FINISH:1]

Зверніть увагу, що до імені змінної mod_rewrite додає 'REDIRECT_'.

Другий спосіб - перевірити в THE_REQUEST, що саме було запрошено користувачем:

# Внешний редирект происходит только если пользователь запросил info.html.
# Если же info.html - это результат внутреннего перенаправления, правило срабатывать не будет.
RewriteCond %{THE_REQUEST} "^(GET|POST|HEAD) /info.html HTTP/[0-9.]+$"
RewriteRule ^info.html$ information.html [R,L]

RewriteRule ^information.html$ info.html

Аналіз вихідного запиту користувача - боротьба з розкриттям посилань Apache

При обробці запиту Apache розкриває закодовані (URL-encoded) символи з початкового запиту. У деяких випадках це може бути небажано - розробник хоче перевіряти саме початковий, не модифікований запит користувача. Зробити це можна, перевіряючи в RewriteCond змінну% {THE_REQUEST}:

RewriteCond %{THE_REQUEST} ^GET[\ ]+/tag/([^/]+)/[\ ]+HTTP.*$
RewriteRule ^(.*)$ index.php?tag=%1 [L]

Рекомендована документація

Офіційна документація Apache

Technical details