Что такое mod_rewrite. Определение кодировки htaccess php. Всегда использовать WWW перед именем домена

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

В предыдущих частях мы изучили практически всю документацию по mod_rewrite . Остались директивы RewriteMap и RewriteOptions . RewriteMap также используется для перезаписи URL адресов, но применяется реже других; к ней мы вернёмся позже. Директива RewriteOptions также применяется нечасто. Особенностью RewriteMap является то, что её нельзя использовать в .htaccess . Её можно использовать только в контексте сервера, либо виртуальных хостов. По большому счёту, RewriteMap не добавляет новой функциональности - она только позволяет вынести большой массив данных, которые нецелесообразно или слишком сложно описывать при помощи регулярных выражений, в отдельные файлы. Получаются такие выделенные базы данных. Тем не менее, мы всё равно рассмотрим RewriteMap в одной из последующих частей.

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

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

Проверка доступности mod_rewrite

Как включить RewriteEngine

О включении модуля mod_rewrite в конфигурационном файле Apache было рассказано в . Если модуль включен, то его необходимо активировать в файле .htaccess директивой :

RewriteEngine On

Это достаточно сделать один раз, даже если вы используете несколько правил перезаписи.

Для работы модуля также необходима активация опции FollowSymLinks . Эта опция может быть активирована в конфигурационном файле Apache (об этом также уже было сказано в первой части). Если эта опция отключена на уровне веб-сервера (или виртуального хоста), то её можно включить в файле.htaccess. Её нужно указать до директивы RewriteEngine :

Options +FollowSymLinks RewriteEngine On

Как проверить, включён ли mod_rewrite

Как проверить в PHP включён mod_rewrite или нет

Самым простым способом является использование функции phpinfo() . Если модуль включён, то в таблице apache2handler в колонке Loaded Modules будет указано mod_rewrite (а также все другие модули, которые включены).

Этот способ является самым универсальным: вы можете использовать его в любой системе, в том числе на совместном (shared) хостинге.

Как проверить в Windows включён ли mod_rewrite

Откройте командную строку (Win+x , затем выберите Windows PowerShell ). Перейдите в каталог, где размещены бинарные файлы Apache. Например, в моём случае это папка C:\Server\bin\Apache24\bin\:

Cd C:\Server\bin\Apache24\bin\

И выполните там команду:

./httpd.exe -M

Будет выведен полный список модулей.

Как проверить в Linux включён ли mod_rewrite

Чтобы вывести список всех загруженных веб-сервером Apache модулей, используется опция -M . Исполнимый файл веб-сервера может называться apache2ctl или httpd в зависимости от используемого дистрибутива.

Для Debian, Ubuntu, Kali Linux, Linux Mint и их производных команда для вывода списка модулей следующая:

Apache2ctl -M

Для Arch Linux, BlackArch и некоторых других дистрибутивов команда такая:

Httpd -M

Проверка включён ли mod_rewrite с помощью.htaccess

В файле .htaccess запишите директиву:

RewriteEngine on

И попробуйте открыть адрес папки, где вы сохранили.htaccess, если возникнет ошибка «500 Internal server error», значит модуль mod_rewrite не включён в конфигурационном файле Apache.

Как сделать так, чтобы правила перезаписи использовались только если mod_rewrite включен

Конструкция проверяет, включён ли модуль. Если модуль включён, то выполняются директивы, которые находятся в секции . Если модуль отключён, то эти директивы игнорируются. В результате, если модуль выключен, то неизвестные директивы не вызовут ошибку веб-сервера.

Синтаксис использования:

…… ……

Вместо многоточий запишите желаемые директивы mod_rewrite, пример:

RewriteEngine On RewriteCond %{HTTP_USER_AGENT} ^HTTrack RewriteCond %{HTTP_USER_AGENT} ^sqlmap RewriteCond %{HTTP_USER_AGENT} ^wpscan RewriteCond %{HTTP_USER_AGENT} ^text RewriteRule ^.* - [F]

Перед именем модуля можно поставить ! (восклицательных знак) и тогда то, что внутри IfModule будет выполнено только если проверяемый модуль НЕ включён.

Секции можно использовать внутри другой секции и выполнять простые тестирования нескольких модулей в зависимости от условия предыдущих тестов модулей.

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

Использование mod_rewrite для перенаправления (редиректа) и переназначения URL

Страница поменяла адрес, как показать новую страницу по старому адресу без редиректа

Описание:

Предположим, мы недавно переименовали страницу foo.html в bar.html и теперь хотим, чтобы старый URL также работал для обратной совместимости. Однако мы хотим, чтобы пользователи старого URL-адреса даже не узнали, что страницы были переименованы, то есть мы не хотим, чтобы адрес изменялся в их браузере.

Мы с помощью RewriteRule делаем преобразования запроса, содержащего старый адрес, на новый, задав следующее правило:

RewriteEngine on RewriteRule "^/foo\.html$" "/bar.html"

В этом примере ^/foo\.html$ является регулярным выражением. Символы ^ и $ обозначают начало и конец строки соответственно. Перед точкой стоит слеш, чтобы символ трактовался буквально (как точка), а не как подстановочный символ (в качестве подстановочного символа точка означает любой один символ).

Страница поменяла адрес, как перенаправить на новую страницу при запросе старой (редирект)

Описание:

Предположим еще раз, что мы недавно переименовали страницу foo.html в bar.html и вновь хотим, чтобы старый URL работал для обратной совместимости. Но на этот раз мы хотим, чтобы пользователи старого URL-адреса получили намек на новый, т. е. поле адресной строки их веб-браузера должно измениться.

Мы принудительно перенаправляем HTTP на новый URL-адрес, что приводит к изменению адреса страницы в браузере и, следовательно, того, что показано пользователю:

RewriteEngine on RewriteRule "^/foo\.html$" "bar.html" [R]

Кстати, для простых случаев редиректа можно использовать директиву Redirect . Эта директива не смогла бы заменить первый пример, когда мы показываем содержимое другой страницы без смены адреса (без редиректа). С Redirect второй пример выглядел бы так:

Redirect "/foo.html" "/bar.html"

Переадресация при смене домена

Описание:

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

Вы можете использовать mod_rewrite для перенаправления этих URL на новый домен, но также рассмотрите вариант с использованием директив Redirect или RedirectMatch .

В последующих примерах замените example.com на адрес сайта, куда должен выполняться редирект.

# С mod_rewrite RewriteEngine on RewriteRule "^/docs/(.+)" "http://new.example.com/docs/$1"

Правило означает найти запросы, которые содержат строку, которая начинается с /docs/ (символ ^ означает начало строки, а /docs/ – это буквальная последовательность символов), за которой затем следует что угодно (точка означает любой символ, а знак плюс означает один или более раз). Скобки образуют обратную ссылку. Т.е. то, что совпадает с выражением в скобках, можно использовать в дальнейшем, сославшись на это с помощью $1 .

В строке перезаписи http://new.example.com/docs/ является буквальной частью, а $1 - это то, что совпало с частью выражения в скобках, т.е. обратная ссылка на (.+) .

Таким образом, если был сделан запрос http://another.com/docs/best, то будет сделана переадресация на адрес http://new.example.com/docs/best.

# С RedirectMatch RedirectMatch "^/docs/(.*)" "http://new.example.com/docs/$1" # С Redirect Redirect "/docs/" "http://new.example.com/docs/"

Директивы Redirect и RedirectMatch должы быть «легче» для сервера, но не всегда сложные случаи можно описать без использования mod_rewrite.

Простой редирект на новый сайт

Если сайт сменил домен и не сохранил структуру страниц, т.е. если вам нужно перенаправить все запросы на новый сайт (например, на его главную страницу), то это делается так:

RewriteEngine on RewriteRule ^ https://newsite.ru

В результате независимо от запрошенной страницы, все запросы будут переданы на главную страницу другого домена. Замените https://newsite.ru на тот сайт, куда вы перенаправляете запросы.

Как переправить все запросы из одной директории, в другую

Псевдоним для единичной директории:

RewriteEngine On RewriteRule ^source-directory/(.*) /target-directory/$1

Все обращения к содержимому директории source-directory будут переадресованы к содержимому директории target-directory.

Использовать URL адресов без расширения файлов.php

Этот снипет позволяет вам использовать URL без расширения PHP, например, example.com/users вместо example.com/users.php.

RewriteEngine On RewriteCond %{SCRIPT_FILENAME} !-d RewriteRule ^([^.]+)$ $1.php

Универсальный документ ошибки (Error Document) для не найденных ресурсов (ошибка 404 Not Found)

Следующее правило выводит указанный вами файл в случае возникновения ошибки 404 Not Found. Обратите внимание, что вам самим нужно указать правильный код ответа HTTP 404 в заголовках ответа (в PHP коде, например).

RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^ /dir/error.php

Если это правило перезаписи вызовет ошибку сервера, то замените флаг на . Флаг подходит лучше, но поддерживается Apache 2.4 и не поддерживается версией Apache 2.2.

Вместо /dir/error.php нужно указать путь до файла, который вы хотите показывать в случае возникновения ошибки 404 (файл не найден).

Со статики на динамику

Описание:

Как мы можем трансформировать статичную страницу foo.html в динамичный вариант foo.cgi бесшовным образом, т.е. без уведомления браузера/пользователя.

Мы просто переписываем URL на CGI-скрипт и принуждаем обработчик быть cgi-скриптом так, что он выполняется как CGI программа. Таким образом, запрос /~quux/foo.html внутренне приводить к вызову /~quux/foo.cgi.

RewriteEngine on RewriteBase "/~quux/" RewriteRule "^foo\.html$" "foo.cgi"

Обратная совместимость для изменений расширения файла

Описание:

Как мы можем сделать обратную совместимость URL (виртуально ещё существующих) после миграции document.YYYY в document.XXXX, например, после перехода ряда.html файлов на.php?

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

# набор правил для обратной совместимости # для перезаписи document.html на document.php # только тогда, когда document.php существует RewriteEngine on RewriteBase "/var/www/htdocs" RewriteCond "$1.php" -f RewriteCond "$1.html" !-f RewriteRule "^(.*).html$" "$1.php"

Обсуждение

В этом примере используется часто забываемая возможность mod_rewrite, вытекающая из порядка выполнения набора правил. В частности, mod_rewrite оценивает левую сторону RewriteRule (Шаблон поиска), прежде чем оценивать директивы RewriteCond. Следовательно, $1 уже определён к тому времени, когда оцениваются директивы RewriteCond. Это позволяет нам проверять наличие исходного (document.html) и целевого (document.php) файла с использованием того же базового имени файла.

Этот набор правил предназначен для использования в контексте директорий (в блоке или в файле.htaccess), так что проверки -f смотрят в каталог по правильному пути. Возможно, вам потребуется установить директиву RewriteBase, чтобы указать базу каталогов, в которой вы работаете.

Замена на WebP изображения

Если поддерживаются WebP изображения, и изображение с файловым расширением.webp найдено в том же месте, где на сервере находится картинка jpg/png, то вместо неё будет отправлено изображение WebP.

RewriteEngine On RewriteCond %{HTTP_ACCEPT} image/webp RewriteCond %{DOCUMENT_ROOT}/$1.webp -f RewriteRule (.+)\.(jpe?g|png)$ $1.webp

Канонические имена хостов и URL. HTTPS

К одной и той же странице можно обратиться по-разному. Например, главную страницу сайта можно открыть любым из следующих методов:

  • http://www.yoursite.com/
  • http://yoursite.com/
  • http://www.yoursite.com
  • http://yoursite.com
  • http://www.yoursite.com/index.php
  • http://yoursite.com/index.php
  • http://yoursite.com/?

Вариантов может быть даже больше, если сайт доступен и на HTTP, и на HTTPS. Также варианты могут возникнуть из-за различных ошибок составления ссылок, при которых страница продолжает открываться. Например:

  • http://www.yoursite.com//index.php

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

Поисковые системы стали значительно более продвинутыми, но не нужно рассчитывать только на них в вопросе борьбы с диблирующими страницами. К тому же, это может внести путаницу в аналитику (когда для каждого из перечисленного примера доход или посещаемостью считаются отдельно, хотя это одна и та же страница).

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

Как сделать редирект с HTTP на HTTPS

Помните, что для использования HTTPS протокола недостаточно просто сделать переадресацию, также должен быть настроен веб-сервер. То есть вы должны получить сертификаты и указать их в настройках хоста. Также веб-сервер должен быть настроен на прослушивание 443 порта. Если это всё готово, то для перенаправления на HTTPS, в файл .htaccess добавьте строки:

RewriteEngine on RewriteCond %{HTTPS} !on RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI}

В этом примере переменная %{HTTPS} содержит on , если сайт использует HTTPS и содержит off , если используется HTTP. Таким образом, адрес страницы переписывается только если к ней обращаются по HTTP.

В RewriteRule в качестве шаблона поиска используется ^ – символ начала строки. Т.е. под это условие подпадают все строки. Цель переадресации указывается с помощью буквальной строки https:// и двух переменных окружения %{HTTP_HOST} и %{REQUEST_URI} .

Strict-Transport-Security: max-age=31536000; includeSubDomains

Как сделать редирект на с HTTP на HTTPS всех страниц кроме некоторых

Предположим, что нам нужно перевести на HTTPS все страницы кроме тех, которые находятся в папке /.well-known/ , тогда используется следующая конструкция:

RewriteEngine on RewriteCond %{HTTPS} !on RewriteCond %{REQUEST_URI} !^/.well-known/ RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI}

Замените /.well-known/ на желаемую папку или адрес страницы.

Если нужно исключить несколько страниц или каталогов, то составьте регулярное выражение с альтернативным выбором, т.е. с использованием трубы (| ). Например, нужно включить переадресацию на HTTPS для всех страниц кроме находящихся в папке /.well-known/, в папке /test/, а также файла /stay-away.php:

RewriteEngine on RewriteCond %{HTTPS} !on RewriteCond %{REQUEST_URI} !^(/.well-known/|/test/|/stay-away.php) RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI}

Как сделать редирект на с HTTP на HTTPS только некоторых страниц

Если вам нужно перенаправить с HTTP на HTTPS только отдельные страницы, то подойдут показанные ранее примеры. Единственное необходимое в них изменение - убрать восклицательный знак (! ), который служит для отрицания совпадения.

Для настройки редиректа на HTTPS только для папки /.well-known/

RewriteEngine on RewriteCond %{HTTPS} !on RewriteCond %{REQUEST_URI} ^/.well-known/ RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI}

Для настройки редиректа на HTTPS только для папки /.well-known/, папки /test/, а также файла /stay-away.php:

RewriteEngine on RewriteCond %{HTTPS} !on RewriteCond %{REQUEST_URI} ^(/.well-known/|/test/|/stay-away.php) RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI}

Принудительное использование HTTPS за прокси

Полезно, если у вас есть прокси-сервер перед вашим сервером, отключающий TLS.

RewriteCond %{HTTP:X-Forwarded-Proto} !https RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}

Всегда использовать WWW перед именем домена

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

RewriteEngine on RewriteCond %{HTTP_HOST} ^example\.com RewriteRule ^(.*)$ http://www.example.com/$1

Обратите внимание, что example.com нужно заменить на домен вашего сайта, вместо протокола http:// может быть указано https:// , а в строке ^example\.com слеш перед точкой не случаен - эта строка является регулярным выражением, чтобы точка рассматривалась не как подстановочный символ, а как буквальная точка, используется слеш.

Всегда использовать WWW перед именем домена - универсальный вариант

Этот вариант подойдёт без изменений для любых сайтов: не нужно указывать имя хоста (доменное имя), а также не нужно указывать, используется ли протокол HTTP или HTTPS. Т.е. это более универсальный вариант.

RewriteEngine On RewriteCond %{HTTP_HOST} !="" RewriteCond %{HTTP_HOST} !^www\. RewriteCond %{HTTPS}s ^on(s)| RewriteRule ^ http%1://www.%{HTTP_HOST}%{REQUEST_URI}

Первое условие проверяет, не является ли значение Host пустым (в случае HTTP/1.0). Второе проверяет, не начинается ли Host на www ..

Обратите внимание на RewriteCond %{HTTPS}s ^on(s)| . Здесь используется довольно хитрый приём. Как было сказано чуть выше, переменная окружения %{HTTPS} содержит on , если сайт использует протокол HTTPS , и содержит off , если используется HTTP . К переменной окружения добавлена буквальная буква s , в результате происходит проверка строки %{HTTPS}s , которая, в зависимости от того, включен ли HTTPS или нет, может сводиться к ons или offs . Эта строка сравнивается с регулярным выражением ^on(s)| , где ^ – это символ начала строки. Символ трубы (| ) говорит о том, что подойдёт любая альтернатива - стоящая перед этим символом или после. Перед этим символом стоит строка on(s) , а после - ничего. Пустая строка соответствует любой сравниваемой строке. Исходя из этого, результат RewriteCond всегда будет сводиться к истине. Но в зависимости от того, какая часть регулярного выражения совпала: on(s) или пустая строка, обратная ссылка будет иметь значение «s » или будет пустой строкой. Обратная ссылка задаётся скобками, в которых находится буква s .

В результате http%1 при RewriteRule будет сводиться к https или к http .

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

Никогда не использовать WWW перед именем домена

Если вам не нужно , чтобы перед доменом в строке браузера были буквы www , то используйте следующее правило:

RewriteEngine on RewriteCond %{HTTP_HOST} ^www\.example\.com RewriteRule ^(.*)$ http://example.com/$1

В нём замените http://example.com на имя вашего домена. Также обратите внимание на протокол. Во второй строке слеши используются для того, чтобы точки в регулярном выражении трактовались как буквальные символы (а не подстановочные).

Никогда не использовать WWW перед именем домена - универсальный вариант

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

RewriteEngine on RewriteCond %{HTTP_HOST} ^www\. RewriteCond %{HTTPS}s ^on(s)|off RewriteCond http%1://%{HTTP_HOST} ^(https?://)(www\.)?(.+)$ RewriteRule ^ %1%3%{REQUEST_URI}

В этом наборе условий и правила не нужно указывать свой домен - конструкция является универсальной для любого сайта, также подходит для сайтов на HTTP и HTTPS.

Принудительное использование канонического имени с HTTPS и www

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

Первый способ:

RewriteEngine On RewriteCond %{HTTPS} off RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} RewriteCond %{HTTP_HOST} !^www\. RewriteRule .* https://www.%{HTTP_HOST}%{REQUEST_URI}

В этом примере имеются два правила перезаписи. Первое перенаправляет на HTTPS. Второе правило перезаписывает любой запрос с неверным доменом на использование www. Флаг означает совпадение независимо от регистра.

Второй способ:

RewriteEngine On RewriteCond %{HTTP_HOST} (?!^www\.)^(.+)$ RewriteCond %{HTTPS} off RewriteRule ^ https://www.%1%{REQUEST_URI}

Третий способ:

RewriteEngine on RewriteCond %{HTTP_HOST} !^$ RewriteCond %{HTTP_HOST} !^www\. RewriteCond %{HTTPS}s ^on(s)| RewriteRule ^ http%1://www.%{HTTP_HOST}%{REQUEST_URI} RewriteCond %{HTTPS} off RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI}

Четвёртый способ (замените domain.ru на свой домен):

RewriteEngine On RewriteCond %{HTTP_HOST} !^www\.domain\.ru RewriteRule ^(.*)$ https://www.domain.ru/$1 RewriteCond %{SERVER_PORT} 80 RewriteRule ^(.*)$ https://www.domain.ru/$1

Канонический вид с HTTPS и без www

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

RewriteEngine on RewriteCond %{HTTPS} !on RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} RewriteCond %{HTTP_HOST} ^www\. RewriteCond %{HTTPS}s ^on(s)|off RewriteCond http%1://%{HTTP_HOST} ^(https?://)(www\.)?(.+)$ RewriteRule ^ %1%3%{REQUEST_URI}

Принудительное SSL и www для главного домена, принудительное SSL без www для всех поддоменов (кроме локальных)

RewriteEngine On # для поддоменов: принудительно ssl и без www RewriteCond %{HTTP_HOST} !\.local$ RewriteCond %{HTTPS} !=on RewriteCond %{HTTP_HOST} !^(www\.)?domain\.ru$ RewriteCond %{HTTP_HOST} ^(?:www\.|)(.*)$ RewriteRule ^.*$ https://%1%{REQUEST_URI} # для главных доменов: принудительно ssl без www RewriteCond %{HTTP_HOST} !\.local$ RewriteCond %{HTTPS} !=on RewriteCond %{HTTP_HOST} ^domain\.ru$ RewriteRule ^.*$ https://www.domain.ru%{REQUEST_URI}

Замените domain.ru на имя вашего домена.

Принудительное добавление конечного слеша к адресу сайта

Если вам нужно добавить к URL конечный слеш (в том случае, если он отсутствует), то воспользуйтесь этим правилом перезаписи:

RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*[^/])$ /$1/

Удаление конечного слеша

Этот сниппет перенаправит пути, заканчивающиеся на слеши, на аналогичные, но без конечного слеша (кроме действительных директорий), к примеру http://www.example.com/blog/ на http://www.example.com/blog. Это важно для SEO, поскольку рекомендуется иметь канонический URL для каждой страницы.

Если вам нужно убрать из URL конечный слеш, то для этого используйте:

RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)/$ /$1

Удаление конечных слешей из произвольных путей

Удаление конечных слешей из URL для веб-сайтов, размещённых в директории (как example.org/blog/):

RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_URI} (.+)/$ RewriteRule ^ %1

Удаление лишних слешей в адресе URL

Например, страница /catalog///stranica.html доступна и открывается. Чтобы избежать такой ситуации и не плодить бесконечное число дублей следует записать следующий редирект:

RewriteEngine on RewriteBase / RewriteCond %{HTTP_HOST} !="" RewriteCond %{THE_REQUEST} ^+\s//+(.*)\sHTTP/+$ RewriteCond %{THE_REQUEST} ^+\s(.*)//+\sHTTP/+$ RewriteRule .* http://%{HTTP_HOST}/%1 RewriteCond %{REQUEST_URI} ^(.*)//(.*)$ RewriteRule . %1/%2

Здесь последовательно используется два правила для того, чтобы удалять многократные слеши из любой части URL: начала, середины, конца.

Эта статья выросла из идеи продвинутого обучения наших сотрудников технической поддержки работе с 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/» тоже отрезалось.

Путь до.htaccess отрезается вместе со слешем. Из этого есть следствие: строка, которая изначально передается на обработку RewriteRule никогда не начинается со "/".

Важно запомнить, что не делает RewriteRule. Она не обрабатывает имя сайта, аргументы, которые переданы в скрипт, да и ссылку обрабатывает не всю, если.htaccess размещен не в корне сайта. Всем этим занимается RewriteCond, которого кратко коснемся чуть позже. Итак:

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

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

# Будет работать только если.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..."

# "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"

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


Что будет, если не указать 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.

#

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


Пример выше приведет к бесконечному циклу перенаправлений и к «Internal Server Error» в итоге. В этом примере бесконечный цикл очевиден, однако в более сложных конфигурациях может потребоваться покопаться в правилах, чтобы определить, какие запросы зацикливаются между собой.
  1. Когда используется внешний редирект - или . В случае внешнего редиректа дальнейшая обработка запроса нежелательна (см. ниже про флаг [R]), и ее лучше остановить.
  2. Когда в.htaccess есть зацикливание, от которого не избавиться, и обработку запроса mod_rewrite"ом нужно принудительно прекратить. В этом случае используется специальная конструкция - см. в конце статьи советы на эту тему.
А вот приведенный ниже пример зацикливаться не будет. Попробуйте определить, почему, и какой в итоге файл будет отдан Apache"м.
# Запрос: http://example.com/a.html
# Начало.htaccess

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

# Конец.htaccess


Отгадка: В результате выполнения всех RewriteRule запрос меняется таким образом, что конечный результат равен исходному . Apache видит это и не запускает повторную обработку запроса . Будет возвращен файл a.html .

Как работает 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] можно указывать просто внешнюю ссылку. В этом случае Apache сам догадается, что необходимо сделать внешний редирект. Здесь, как и с в случае с явным указанием флага [R], рекомендуется использовать флаг [L].
  • Если внешний редирект ведет на тот же сайт, лучше использовать флаг [R] без указания полной ссылки (иными словами, использовать относительный внешний редирект). Это сделает правило независимым от имени сайта.
  • Если же внешний редирект ведет на другой сайт, иначе, как указав полную внешнюю ссылку, это сделать не получится.

Как работает mod_rewrite. Указание параметров запроса и флаг

Изменение параметров запроса в 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/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}. Если был указан флаг , параметры запроса будут добавлены в начало %{QUERY_STRING}. Если флаг указан не был, %{QUERY_STRING} полностью заменится параметрами запроса из RewriteRule.
Еще пара примеров:
RewriteBase /

#

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
# После преобразования: "news/2010/" -> "index.php"
# Значение %{QUERY_STRING}: "page=2" -> "act=news&what=2010/&page=2"


Мы добавили только флаг , и правило стало работать корректно.

Важно понимать, что изменение параметров запроса изменяет %{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] и , а также разобрали механизм обработки запросов внутри 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{2}/{2}/{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
# но по запросу /information.html все равно отдать info.html
RewriteRule ^information.html info.html

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

Решить эту проблему можно как минимум двумя способами. На Хабре был уже описан один из них - нужно установить переменную окружения и на основании ее значения прекращать перенаправления. Код будет выглядеть следующим образом:

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


RewriteRule ^information.html$ info.html


Обратите внимание, что к имени переменной mod_rewrite добавляет "REDIRECT_".

Второй способ - проверить в THE_REQUEST, что именно было запрошено пользователем:

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

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 . Да-да.

Большое спасибо за внимание!

Теги:

Добавить метки

andew

2015-02-13T11:59:58+00:00

2018-03-02T04:50:41+00:00

34141

В статье я привожу описание логики работы правилаRewriteRule и синтаксис некоторых директив модуля mod_rewrite сервера Apache. Также я выделил и обобщил несколько выводов-постулатов, которые, как мне кажется, нужно обязательно знать и понимать при использовании этого модуля. Надеюсь, что все это позволит вам, так же, как и мне ранее, разобраться с работой этого модуля, предоставляющего мощный функционал для выполнения различных преобразований над URL .

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

Также не забывайте оборачивать весь блок правил для mod_rewrite в тег:

Не пишите излишне много директив для mod_rewrite , пишите только те правила преобразования, которые вам действительно необходимы. Особенно нужно быть аккуратным с внешними ридиректами - это такие ридиректы, которые выполняются путем отправки клиенту серверного заголовка с кодом НЕ 200 (отдача полноценной страницы), а с другим кодом (чаше всего 301 и 302 ) и которые приводят к перенаправлению в браузере клиента на другой URL т.е. к совершению нового запроса на клиенте. Поэтому любой внешний ридирект всегда приводит к потере времени в обработки запроса, т.к. нужно отправить клиенту ответ, он должен его прочитать, и повторить запрос уже по новому URL. Это затратная по времени процедура. Поэтому ридиректы должны быть только если они действительно вам необходимы.

Учитывайте, что браузеры могут кешировать редиректы, при этом Ctrl+F5 или Ctrl+R не снимает проблему. Поэтому отключайте кеширование в браузере при тестировании ваших правил rewrite модуля web сервера Apache .

Для поиска ошибок работы ваших правил читайте логи Apache .

Не делайте тупой копи-паст директив из различных статей в интернете в свои файлы настроек apache. Cтатьи пишут люди и поэтому возможны ошибочные написания директив, и если учесть, что метод написания статей также подразумевает активное использование копипаста, то ошибки могут распространяться и множиться. Используйте статьи только как справку, а в свои настроечные файлы вписываете директивы из руководств. Хотя это тоже не гарантирует на сто процентов от ошибок, но хотя бы вы из минимизируете.

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

Почему то на просторах рунета информация о локальной настройки веб-сервера Apache посредством конфигурационного файла .htaccess приводится как то не полно и однобоко. В основном приводятся примеры (часто не рабочие) или сухой перевод англоязычной документации.

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

Редиректы

Редиректы осуществляются с помощью модуля mod_rewrite . Задаются правила преобразований в виде следующей конструкции:

Options +FollowSymLinks RewriteEngine On [СЮДА ПИШЕМ ПРАВИЛА]

Правила преобразования записываются в таком виде:

RewriteCond [СТРОКА ДЛЯ СРАВНЕНИЯ] [УСЛОВИЕ] [ФЛАГИ] RewriteCond [СТРОКА ДЛЯ СРАВНЕНИЯ] [УСЛОВИЕ] [ФЛАГИ] RewriteRule [ШАБЛОН] [СТРОКА ПОДСТАНОВКИ] [ФЛАГИ]

Строки RewriteCond - задают условия для срабатывания следующего за ними правила RewriteRule . Условий может быть несколько, они накладываются по правилу AND . Но можно изменить правило на OR с помощью флага OR .

В качестве [СТРОКИ ДЛЯ СРАВНЕНИЯ] могут использоваться различные переменные. Ссылка на полный список Я приведу только те, которые нужны чаще всего:

Подвыражения в регулярных выражениях (заключенные в скобки), доступны для вставки в [СТРОКУ ПОДСТАНОВКИ] , обращаться к подвыражениям нужно так: %N - для подвыражений в условиях (RewriteCond ) и $N - для подвыражений в правилах (RewriteRule ), где N - порядковый номер подвыражения.

RewriteRule - правило подстановки. Если запрос подходит под вышестоящие проверки и [ШАБЛОН] , то применяется правило подстановки. Здесь регулировать поведение также можно с помощью флагов. Флаги есть разные, приведу наиболее часто используемые:

Надеюсь после моего краткого ввода в теорию, вам будет проще понимать что же написано в вашем .htaccess . Привожу ссылку на очень хороший перевод про модуль mod_rewrite , там же можно найти другие хорошие переводы.

Внимание! Браузеры кешируют редиректы!!!

Причем обычные сочетания типа Ctrl+F5 или Ctrl+R не помагают. Я во время тестирования каждый раз открываю страницу в НОВОМ окне в режиме инкогнито. Причем старые страницы в режими инкогнито надо закрывать.

Примеры

Универсальный редирект с www на без www

Тут самое интересное, почему то везде приводятся примеры, жестко привязанные к домену сайта. Зачем?, если есть универсальное решение:

RewriteCond %{HTTP_HOST} ^www\.(.*) RewriteRule ^(.*)$ http://%1/$1

Проверяем доменное имя, если оно начинается с www, то сработает правило: "все, на http://%1/$1 ". Здесь %1 это наш домен без www (взят из условия), а $1 это адрес (взят из самого правила).

Универсальный редирект с без www на www

RewriteCond %{HTTP_HOST} ^(.*)$ RewriteCond %{HTTP_HOST} !^www\. RewriteRule ^(.*)$ http://www.%1/$1

Тут маленько сложнее. Первое условие нужно для того чтобы получить домен (%1 ), оно всегда истина. Второе условие проверяет, что домен начинается не с www. Ну и само правило, аналогичное предыдущему примеру

Простой редирект

RewriteRule ^news/happy.* /news.html

Для простого редиректа условия задавать не обязательно, только правило.

Реврайт без редиректа

RewriteRule ^news/happy.* /news.html [L]

Иногда требуется, чтобы был редирект без смены адреса, т.е. реврайт без редиректа. Для этого просто не указываем флаг редирект (R ), и получаем желаемый результат, теперь по адресу news/happy получим news.html , а в адресной строке останется news/happy

Редирект от GET параметров

Например, нужно что бы со страницы /?action=page&id=15 был редирект на /page/15/ :

RewriteCond %{QUERY_STRING} action=page RewriteCond %{QUERY_STRING} id=(\d+) RewriteRule .* /page/%1/?

Поясню, первым условиям проверяем что есть get параметр action=page , вторым условием проверяем что id равно числу. Эти условия нельзя объединять, т.к. параметры могут идти и наоборот, т.е. index.php?action=page&id=15 и index.php?id=15&action=page должны быть равноценны. Но и наконец правило, там все обычно, кроме знака вопрос (? ) на конце. Он нам нужен, чтобы отсечь исходные GET параметры, иначе получим /page/15/?action=page&id=15

Редирект на мобильную версию сайта

Допустим, что мобильная версия расположена на поддомене m.site.ru . Будем переходить на мобильную версию только с главной страницы основного домена.

RewriteCond %{HTTP_USER_AGENT} (?i:midp|samsung|nokia|j2me|avant|docomo|novarra|palmos|palmsource|opwv|chtml|pda|mmp|blackberry|mib|symbian|wireless|nokia|hand|mobi|phone|cdm|upb|audio|SIE|SEC|samsung|HTC|mot-|mitsu|sagem|sony|alcatel|lg|eric|vx|NEC|philips|mmm|xx|panasonic|sharp|wap|sch|rover|pocket|benq|java|pt|pg|vox|amoi|bird|compal|kg|voda|sany|kdd|dbt|sendo|sgh|gradi|jb|dddi|moto|iphone|android) RewriteCond %{HTTP_HOST} site.ru RewriteRule ^$ http://m.site.com/

Первой строкой мы проверяем USER_AGENT, определяем что он относится к мобильникам. (эту строку я детально не проверял, взял на просторе интернета, возможно она не совсем корректная, или есть более универсальная строка. Но на моих мобильных устройствах этот пример работает)

Второй строкой проверяем что мы находимся на нужном домене (т.к. пример не универсальный)

Третьей строкой, мы проверяем, что находимся на главной страницы (без всяких параметров и прочего) и перенаправляем на поддомен.

Универсальная версия

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

RewriteCond %{HTTP_HOST} ^(.*)$ RewriteCond %{HTTP_USER_AGENT} (?i:midp|samsung|nokia|j2me|avant|docomo|novarra|palmos|palmsource|opwv|chtml|pda|mmp|blackberry|mib|symbian|wireless|nokia|hand|mobi|phone|cdm|upb|audio|SIE|SEC|samsung|HTC|mot-|mitsu|sagem|sony|alcatel|lg|eric|vx|NEC|philips|mmm|xx|panasonic|sharp|wap|sch|rover|pocket|benq|java|pt|pg|vox|amoi|bird|compal|kg|voda|sany|kdd|dbt|sendo|sgh|gradi|jb|dddi|moto|iphone|android) RewriteRule ^$ http://m.%1

Редирект с главной страницы

Речь идет про запрос типа site.ru (без site.ru/index.php)

Здесь оказалось не все так очевидно, я столкнулся с необъяснимым поведением.

Рабочий вариант :

RewriteRule ^index.php$ /about/ [L]

Редирект. НЕ рабочий вариант :

RewriteRule ^index.php$ /about/

Реврайт без редиректа (урл не меняется). НЕ рабочий вариант :

RewriteRule ^$ /about/ [L]

Редирект. Рабочий вариант :

RewriteRule ^$ /about/

Если мне кто - нибудь расскажет почему эти примеры работают крест накрест, а обратно не работают - буду очень рад.

Htaccess - это дополнительный конфигурационный файл Apache, который позволяет настраивать работу веб-сервера для каждой отдельной директории, не влияя на глобальные настройки Apache. Локальная аналогия httpd.conf . Обычно он отвечает за редиректы и управление доступом к директориям.

Название начинается с точки. Можно сказать, это файл без названия с расширением htaccess.

Настройки.htaccess действуют на каталог, в котором он расположен, и на все дочерние каталоги. Создайте файл и поместите в нужную вам директорию. Например, в корень проекта.

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

mod_rewrite и редиректы

Убедитесь, что в конфигурационном файле Apache httpd.conf активирован mod_rewrite . То есть, раскомментирована соответствующая строка:

LoadModule rewrite_module modules/mod_rewrite.so

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

Sudo a2enmod rewrite

mod_rewrite - это модуль Apache, предназначенный для преобразования URL-ов. Рассмотрим на примере, как он работает. Допустим, пользователь вводит следующий адрес:

C помощью mod_rewrite можно отправить содержание с другого URL, например такого:

Http://www.example.com/public/src/view/page.html

Зачем это нам? Легко догадаться, что писать полный путь до страницы долго и просто неудобно. Посетителям сайта не нужно думать о внутренней структуре сайта - им важно максимально быстро попасть на искомую страницу.

В адресной строке пользователь будет всё также видеть введенное им:

Http://www.example.com/page.html

Это пример самого простого редиректа.

Сразу к практике

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

Php_value short_open_tag 1 php_value upload_max_filesize 10M php_value post_max_size 10M RewriteEngine On RewriteBase / RewriteRule ^(application|modules|system) - RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule .* index.php/$0

  • php_value установка строковых и числовых значений
  • php_flag устанавливает логические значения (да/нет)

Общий синтаксис директив

Php_value/php_flag имя_директивы_php flag/value

Директива short_open_tag разрешает использование короткого синтаксиса для оформления PHP-кода:

Php_value short_open_tag 1

upload_max_filesize определяет максимальный размер загружаемого файла.

Php_value upload_max_filesize 10M

А post_max_size устанавливает максимально допустимый размер данных, отправляемых методом POST.

Php_value post_max_size 10M

RewriteEngine

Включает/выключает механизм mod_rewrite .

RewriteEngine On

RewriteRule

RewriteRule просто преобразовывает строку в соответствии с регулярными выражениями.

Синтаксис: RewriteRule regular_expression

# На входе RewriteRule "index.php" RewriteRule ^index.php main.php [R] # На выходе: "index.php" -> "main.php"

Мы преобразовали index.php в main.php и выполнили редирект.

Важно : RewriteRule обычно принимает два аргумента: что нужно заменить и на что нужно заменить. Если нам не нужно выполнять замену то можно записать в виде:

Символ «-» означает «не преобразовывать»

RewriteBase

После всех RewriteRule, в силу вступает RewriteBase. Если получившийся после преобразований запрос является относительным и отличается от исходного, RewriteBase восстановит его, сделав абсолютным. RewriteBase просто допишет себя к запросу слева. Потому что значение RewriteBase - путь от корня сайта до.htaccess. В нашем случае.htaccess лежит прямо в корне, поэтому:

Синтаксис : RewriteBase URL-path-from-.htaccess-file-to-site-root

Например:

# .htaccess находится в /dir/ # Путь от корня сайта до.htaccess /dir/ RewriteBase /dir/ # Запрос http://example.com/dir/logo.gif # На вход RewriteRule попадает "logo.gif" RewriteRule ^logo.gif$ logo-orange.gif # После RewriteRule: "logo.gif" -> "logo-orange.gif" # После RewriteBase: "logo-orange.gif" -> "/dir/logo-orange.gif"

Regular expressions

Регулярные выражения, которые вам могут встретиться в.htaccess.

Символ Значение Пример
. Один любой символ c.t это cat , cot , cut , и т. д.
+ Один или несколько одинаковых символов a+ это a , aa , aaa , и т. д.
* Ноль или несколько одинаковых символов a* работает также как и a+ но в случае a* условию удовлетворит и пустая строка
? Совпадение опционально colou?r подойдет как color , так и colour .
^ Символ, с которого начинается строка ^a соответствует строка, которая начинается с a
$ Символ, которым заканчивается строка a$ соответствует строка, которая заканчивается a .
() Находит и запоминает соответствие группы символов.

Также может быть использовано для Back-Reference (смотри пример)

(ab)+ удовлетворит ababab

Back-Reference example:

RewriteRule ^/(+) /(.*) $ /home?page=$1 &id=$2

/album/123 → /home?page=album &id=123

Один из возможных символов ct подойдет cut , cot или cat .

Больше regular expressions

Флаги

Синтаксис : RewriteRule regular_expression [флаг1,флаг2,флаг3]

Флаг Описание
[F] Forbidden - возвращает ошибку 403 Forbidden (запрещено).
[L] Last - остановить процесс преобразования на этом месте и не применять больше никаких правил преобразований.
Query String Append - этот флаг указывает механизму преобразований на добавление, а не замену , строки запроса из URL к существующей, в строке подстановки.
PassThrough - останавливает процесс преобразования и передает полученную новую ссылку дальше по цепочке.
[R] Redirect - останавливает процесс преобразования и возвращает результат браузеру клиента как редирект на новую страницу.
[S] Skip - пропускает следующее правило, если текущее правило сработало. Можно указать количество последующих игнорируемых правил .


Понравилась статья? Поделиться с друзьями: