Централизация обработки запросов к сайту с помощью mod_rewrite
Многие программисты создают на сервере структуру файлов и каталогов, точно соответствующую адресам страниц. Нередко это бывает неудобно: требуется создание разветвленной структуры каталогов, повторение определенного количества программного кода в нескольких местах и др. Модуль веб-сервера Apache mod_rewrite позволяет централизовать обработку запросов и сделать ответ сервера независимым от расположения физических файлов и каталогов — грубо говоря, решать, что показать клиенту, будет не файловая система и программист, а только программист, в единоличном порядке. О том, как это осуществить, и пойдет речь в настоящей статье.
Для чего это нужно.
На многих сайтах встречаются страницы вида /news.php?id=123, /products.php?id=45, /about.php, /contact.php и т.п. Как правило, каждой такой странице соответствует отдельный скрипт с точно совпадающими названием и расположением, что уже неприятно, т.к. накладывает определенные ограничения и, таким образом, увеличивает вероятность ошибки при разработке сайта. Что еще (и гораздо) более неприятно: большинство страниц сайта имеет повторяющиеся фрагменты HTML, которые генерируются одним и тем же кодом в скриптах; соответственно, этот код должен быть повторен столько раз, сколько есть скриптов.
Что может дать описываемый подход? Для начала он позволит избавиться от нескольких файлов, не отказываясь при этом от вида адресов /scriptname.php?get=..., и заменить их одним рабочим скриптом (или не одним; в любом случае, столькими, сколькими захочется, а не сколькими потребует формат адреса).
На самом деле такой подход может дать гораздо больше. При таком подходе относительный адрес страницы перестает зависеть от физического расположения файлов и каталогов и становится некой абстрактной строкой, вид которой ограничивается только стандартом URL. Это значит, что при формировании ответа клиенту можно сделать вид, что есть, например, каталог /news/ и страница /news/123.html (или что-то еще более сложное), хотя ни каталога, ни файла с такими названиями в физической файловой системе нет и в помине.
Замена адресов вида /news.php?id=123 и /articles.php?id=45 на адреса вида /news/123.html и /articles/45.html отнюдь не лишена смысла: такой формат адресов обладает лучшей читаемостью, кроме того, есть информация, что для поисковых систем также легче проиндексировать две фактически разные страницы, если они имеют разные адреса, чем в случае, когда адреса отличаются только строкой GET-запроса.
Что такое mod_rewrite.
mod_rewrite — один из модулей веб-сервера Apache. mod_rewrite предоставляет мощные и гибкие средства для разнообразных манипуляций с URL. Для решения настоящей задачи, однако, понадобится лишь малая часть функциональности этого модуля, которая и будет рассмотрена ниже. mod_rewrite посвящен раздел официального сайта Apache.
.htaccess
Одним из самых гибких методов настройки сервера Apache является использование конфигурационных файлов .htaccess. Установки из этих файлов имеют более высокий приоритет, чем установки в конфигурационных файлах httpd.conf сервера Apache (если не запрещена замена установок), и действуют на каталог, в котором находится данный файл .htaccess, а также на все дочерние каталоги (если только в каких-либо из них нет своих файлов .htaccess — тогда установки из .htaccess текущего каталога аннулируют установки из .htaccess родительского).
Сейчас наша основная задача состоит в том, чтобы централизовать управление сервером — передать его какому-то одному скрипту, пусть это будет, например, скрипт index.php в корневом каталоге веб-сервера. Поскольку мы хотим перенаправлять таким образом абсолютно любой запрос, файл .htaccess следует создать также в корневом каталоге. Для решения нашей задачи этот файл должен содержать всего две строчки:
RewriteRule .*? index.php
Директива RewriteEngine on отвечает за включение механизма mod_rewrite (без неё он просто не начнет работать). Директива RewiteRule не так проста в использовании и также является исключительно важной в описываемой реализации, поэтому обсудим её подробнее.
Как уже говорилось, основная часть задачи состоит в том, что необходимо передать управление. Этим как раз и занимается директива RewriteRule. Синтаксис использования этой директивы в .htaccess таков:
В качестве условия выступает Perl-совместимое регулярное выражение (mod_rewrite использует библиотеку PCRE), которое сравнивается со строкой URL (если запрошен адрес http://site.ru/news/123.html, то строкой URL будет /news/123.html). Поскольку нам требуется запустить механизм в любом случае, следует составить такое регулярное выражение, которое совпало бы с любой строкой. Для этого хорошо подходит .*?.
Второй аргумент директивы (что_делать) — это указание серверу о том, как ему изменить адрес запроса. Нам нужно абсолютно все запросы направить к скрипту index.php, находящемся в том же каталоге, что и .htaccess; это и отражено в директиве.
Что теперь должен делать index.php
Вот как может выглядеть вышеупомянутый скрипт index.php:
/*
Здесь и должен формироваться код для всех возможных URL (т.е. для всех страниц).
Если раньше всё это было рассредоточено по разным файлам, то теперь всё - здесь.
По непосредственно рабочему коду отличия минимальные: как формировали HTML специфические
предназначенные для этого функции, так и будут формировать.
Главное - не забыть, что одинаковые части страницы
типа шапки формируются один раз, и не наделать повторяющегося кода.
Следует отметить, что несмотря на манипуляции с адресом запроса,
cодержимое переменной $_SERVER['REQUEST_URI'] останется прежним,
и можно будет работать с тем адресом запроса, который был в самом начале.
*/
$url = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); // GET-запрос пока уберём
// разберем различные случаи вида url.
if ($url == '/') {
// здесь выводим HTML для главной страницы; её адрес - /
// хотя более удобным было бы для всякой страницы HTML-код
// всё же формировать динамически
}
elseif (preg_match('/^\/news\/(\d+)\.html$/', $url, $matches) {
// обрабатываем страницы вида /news/123.html
$newsid = $matches[1];
// далее получаем текст новости по id, внедряем текст в код страницы и т.п.
}
elseif (preg_match('/^\/articles\/(.+)\.html$/', $url, $matches))
// обрабатываем страницы вида /articles/some_article.html
$article_name = $matches[1];
// получаем данные статьи по её имени
}
else {
/*
Если ничего из вышеперечисленного не сработало, значит, запрошенный адрес
не относится ни к статье, ни к новости, а относится к чему-либо еще
(например, к одной из уникальных страниц, вид которых
строится индивидуально для каждой из них).
Возможно, страницы с запрашиваемым адресом на сайте не существует,
тогда клиенту нужно вернуть HTTP-код ошибки - 404 - и информацию о том,
что страница не найдена.
*/
}
/*
Теперь можно каким-либо способом (например, при помощи какого-нибудь шаблонизатора)
построить однотипные для всех страниц сайта фрагменты HTML-кода
(например, объявление DOCTYPE, заголовок и шапку страницы),
после чего вывести полученный код в окно браузера.
Разумеется, всё это может принимать сколь угодно разнообразные формы.
*/
?>
Если все-таки кое-где нужны физические файлы и каталоги.
Бывает, однако, так, что в ряде случаев по тем или иными причинам использовать физические файлы и каталоги удобнее, чем формировать динамический ответ — например, в случае картинок или других двоичных файлов (хотя существует альтернативный способ хранения и обработки файлов, основанный на использовании СУБД). В рамках данного подхода это может быть реализовано двумя способами.
Собственные файлы .htaccess в подкаталогах
Как уже упоминалось, установки в .htaccess текущего каталога имеют приоритет над установками родительских всех уровней. Причем эти установки действуют не только на сам текущий каталог, но и на его дочерние каталоги. Этим можно воспользоваться в каждом случае, когда имеется каталог (пусть /pics/), содержимое которого должно быть доступно напрямую. Для этого нужно в нём создать файл .htaccess, содержащий всего одну строчку:
Такой файл просто снимет все имеющиеся предписания насчет перенаправления запросов для каталога /pics/ и его дочерних каталогов, и изнутри /pics/ всё будет выглядеть так, как будто mod_rewrite вообще не включен.
Следует отметить, что понятие «изнутри каталога» относится также и ко вложенным каталогам — доступ к ним тоже будет прямым, для этого создавать .htaccess в каждом из них не нужно.
Условия !-d и !-f в корневом .htaccess
Этот вариант реализуется на уровне корневого каталога и состоит в ограничении правила RewriteRule определенными условиями.
В общем случае каждому правилу (директиве RewriteRule) может предшествовать одно или несколько условий выполнения этого правила (директив RewriteCond). Правило применяется лишь в том случае, если все предшествующие ему условия выполнены.
В примерах выше мы не использовали условий, поскольку правила должны были действовать в любом случае. Теперь же необходимо составить условия — такие, которые запретили бы правилу действовать в случаях, когда запрашиваемый адрес является путем к физическому файлу, или, немного перефразируя, условия дейсвтия должны быть таковы, чтобы запрошенный адрес не являлся путем к файлу. С учетом всего этого .htaccess будет выглядеть так:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .*? index.php
# в переменной сервера REQUEST_FILENAME как раз и хранится адрес запроса
Такая конфигурация обеспечит доступ не только к файлам в текущем каталоге, но и в дочерних (т.е. для того, чтобы стали видны файлы /pics/123.jpg, специально делать больше ничего не понадобится). В то же время, адреса, соответствующие физическим каталогам, по-прежнему будут переписываться. Это приведет к тому, что списки содержимого каталогов просматривать будет нельзя (запросив /pics/, вместо списка картинок клиент получит перенаправление на /index.php). Если все-таки нужна видимость и каталогов тоже, то .htaccess следует дополнить соответствующим условием для каталогов, и он примет такой вид:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .*? index.php
Подробнее о составлении условий можно узнать из соответствующей страницы документации к серверу Apache.
Сравнение двух способов.
Первый способ является более безопасным, поскольку общие установки более строгие. Чтобы их отменить, нужно совершить специальные операции — это и повышает безопасность, поскольку программист яснее осознает, какие области он делает доступными. Это же и снижает удобство: если безопасность не нужна, то легче написать две строчки в корневом .htaccess, чем проводить отдельную работу для каждой директории.
Чего принципиально не может первый метод — так это сделать доступными клиенту файлы в корневом каталоге (для этого придется прописать условие для файлов, что фактически будет означать переход ко второму способу). Однако, доступность физических файлов корневого каталога веб-сервера вряд ли является большим преимуществом. Как правило, там находятся служебные скрипты и файлы, в которых определяются функции. Если к ним получит доступ злоумышленник, в лучшем случае просто ничего не произойдет — это если в них нет исполняемого кода. Вариант, при котором без ведома разработчиков кто угодно сможет запустить скрипт, который что-либо делает, с точки зрения безопасности не стоит даже и обсуждать.
© Все права на данную статью принадлежат порталу webew.ru. Перепечатка в интернет-изданиях разрешается только с указанием автора и прямой ссылки на оригинальную статью. Перепечатка в печатных изданиях допускается только с разрешения редакции.