Обработчики Apache
Итак, что же такое обработчик Apache? На самом деле мы постоянно сталкиваемся с одним из классических примеров обработчика. Да-да, вы уже догадались: это сам PHP. Если чуть углубиться в теорию, то обработчиком
называется сценарий (возможно, встроенный в сам сервер, как это происходит с PHP), который запускается сервером при попытке пользователя открыть ту или иную страницу определенного типа.
Каждый обработчик должен иметь уникальный идентификатор— имя обработчика, который я для краткости буду называть просто именем. Оно может состоять только из алфавитно-цифровых символов и знаков подчеркивания. Заметьте, что это имя — не то же самое, что имя файла сценария, в котором хранится код обработчика. Имя обработчика и является тем, которое нужно указывать серверу в директиве AddHandler, когда мы хотим связать определенные документы с нашим сценарием.
Но как же сопоставить идентификатор обработчика тому сценарию, который содержит его код? У сервера Apache для этого есть специальная директива под названием Action. Где задается эта директива? В любом файле конфигурации Apache. Конечно, удобнее всего это делать в файле .htaccess, расположенном в корневом каталоге виртуального хоста, чтобы изменения распространились сразу на весь сервер. Мы уже рассматривали такую стратегию выше, только теперь все будет чуточку сложнее.
Вот требуемые директивы. Поместим их, как водится, в главный .htaccess-файл хоста.
# Сначала связываем имя обработчика с конкретным файлом.
# Знак "?" говорит серверу, что исходный URL запроса следует
# передать сценарию методом GET, т. е. через QUERY_STRING.
Action libhandler "/lib/libhandler.php?"
# Теперь уведомляем сервер, документы какого типа мы желаем
# "пропускать" через наш обработчик.
AddHandler libhandler .html .htm
В этом фрагменте есть два тонких места.
r Путь к сценарию обработчика всегда
указывается как абсолютный URL без указания имени хоста и порта. Мы не можем задать здесь ни путь к файлу, ни даже относительный URL. По той причине, чтобы позволить одному обработчику "передавать эстафету" другому. В самом деле, ведь это и происходит в нашем примере: Apache сначала определяет, что документ нужно "пропустить" через обработчик libhandler, а т. к. он представляет собой ни что иное, как сценарий на PHP, то и через интерпретатор PHP. В деталях затронутый процесс чуть сложнее, но мы не будем в него углубляться.
r После URL сценария в директиве Action следует знак ?. Зачем он? Это связано с механизмом, который использует Apache для того, чтобы определить конечный обработчик для того или иного документа. Когда пользователь посылает серверу URL, который, как Apache "знает", подходит под одну из команд Action, к этому URL слева просто присоединяется второй параметр директивы, и все начинается сначала — до тех пор, пока не будет найден последний обработчик. Например, если пользователь ввел /dir/file.html, то благодаря директиве Action указанный адрес преобразуется в /lib/libhandler.php?/dir/file.html. Это — ни что иное, как адрес PHP-сценария с параметром, который будет передан программе, как обычно, через переменную окружения QUERY_STRING.
Теперь сервер знает, что все документы с расширением html и htm нужно обрабатывать при помощи сценария, расположенного по адресу /lib/libhandler.php. Точнее, при каждой попытке открыть страницы с указанными расширениями Apache будет запускать наш сценарий и в числе обычных переменных окружения отправлять ему несколько специальных, содержащих первичную информацию о запросе, переданном пользователем. Мы сейчас рассмотрим эти переменные на практике. Если вас интересует их полный список, попробуйте распечатать массив $GLOBALS или воспользоваться функцией phpinfo(), вставив ее первой и единственной командой обработчика libhandler.php.
Вы, возможно, спросите, почему же мы не добавили в список расширений, на которые будет "реагировать" сценарий, еще одно — php? Давайте посмотрим, что бы произошло, поступи мы так. Предположим, пользователь хочет загрузить страницу /a.php. Apache "видит", что расширение у нее — php, и запускает обработчик с именем /lib/libhandler.php. Точнее, он "сваливает" всю работу на сценарий libhandler.php. Еще точнее — перенаправляет сервер по адресу /lib/libhandler.php?a.php! И тут же зацикливается, потому что у этого сценария расширение — также php. Итак, сервер начинает вызывать сценарий снова и снова, все удлиняя его URL — до тех пор, пока не "поймет": что-то неверно, и пора аварийно завершаться, о чем и сообщает в файлах журнала. О том, как решить эту проблему, рассказано в самом конце главы.
Ну вот, у нас уже почти все готово. Осталось только написать сам код обработчика. Это не так уж и сложно. Но прежде давайте вспомним, зачем мы вообще связались с обработчиками. Для автоматической загрузки библиотекаря перед выполнением того или иного сценария, помните? Что же, вот пример (листинг 29.5).
Мы подразумеваем, что обработчик libhandler.php находится в том же самом каталоге, что и библиотекарь с большинством модулей. Это довольно удобно, поскольку позволяет нам задавать путь к каталогу с модулями лишь в единственном месте — в директиве Action файла .htaccess, да и то в виде относительного URL. Оцените, насколько это проще для будущих модификаций сайта.
Листинг 29.5. Обработчик /lib/libhandler.php с подключением библиотекаря
<?
// Прежде всего, устанавливаем свои каталоги поиска модулей.
// Это, по нашей договоренности, — текущий в данный момент каталог.
$INC[]=getcwd();
// Ïðîâåðÿåì, íå ïûòàåòñÿ ëè ïîëüçîâàòåëü çàïóñòèòü îáðàáîò÷èê íàïðÿìóþ,
// ìèíóÿ Apache — íàïðèìåð, ïóòåì íàáîðà â áðàóçåðå àäðåñà
// /lib/libhandler.php. Òàê êàê àäðåñ, ââåäåííûé ïîëüçîâàòåëåì,
// âñåãäà ïåðåäàåòñÿ â ïåðåìåííîé îêðóæåíèÿ REQUEST_URI, òî íóæíî
// "áèòü òðåâîãó", åñëè ïåðåäàííàÿ ñòðîêà àäðåñà âñòðå÷àåòñÿ
// â èìåíè ôàéëà îáðàáîò÷èêà (ïðè÷åì â ëþáîì ðåãèñòðå ñèìâîëîâ).
// Ìû íå çàáûëè îòрåçàòü â ýòîé ñòðîêå ÷àñòü ïîñëå ?, ïîòîìó ÷òî
// îíà áóäåò ìåøàòü ïðè ñðàâíåíèè ñ èìåíåì ôàéëà.
// Ê ñîæàëåíèþ, ïîõîæå, ýòî åäèíñòâåííûé ïåðåíîñèìûé ìåæäó îïåðàöèîííûìè
// ñèñòåìàìè ñïîñîá ïðîâåðêè ëåãàëüíîñòè çàïóñêà îáðàáîò÷èêà.
$FileName=strtr(__FILE__,"\\","/");
$ReqName=ereg_Replace("\\?.*","",strtr(getenv("REQUEST_URI"),"\\","/"));
if(eregi(quotemeta($ReqName),$FileName)) {
// Выводим сообщение об ошибке
include "libhandler.err";
// Записываем в журнал данные о пользователе
$f=fopen("libhandler.log","a+");
fputs($f,date("d.m.Y H:i.s")." $REMOTE_ADDR - Access denied\n");
fclose($f);
// Завершаем работу
exit;
}
// Все в порядке — корректируем переменные окружения в соответствии
// с запрошенным пользователем адресом.
@putenv("REQUEST_URI=".
$GLOBALS["HTTP_ENV_VARS"]["REQUEST_URI"]=
$GLOBALS["REQUEST_URI"]=
getenv("QUERY_STRING")
);
@putenv("QUERY_STRING=".
$GLOBALS["HTTP_ENV_VARS"]["QUERY_STRING"]=
$GLOBALS["QUERY_STRING"]=
ereg_Replace("^[^?]*\\?","",getenv("QUERY_STRING"))
);
// Подключаем библиотекарь
include "librarian.phl";
// Здесь можно выполнить еще какие-нибудь действия...
// . . .
// Запускаем тот сценарий, который был запрошен пользователем
chdir(dirname($SCRIPT_FILENAME));
include $SCRIPT_FILENAME;
?>
Ну и, конечно, какая же программа обходится без вывода диагностических сообщений? Наш пример подгружает файл libhandler.err в случае "жульничества" пользователя. Наверное, в нем следует написать что-то типа:
<head><title>Доступ запрещен!</title></head>
<body>
<h2>Доступ запрещен!</h2>
Пользователь сделал попытку нелегально вызвать обработчик Apache,
отвечающий за автоматическое подключение библиотекаря. Так как это
свидетельствует о его желании нелегально проникнуть на сервер,
попытка была пресечена. Информация о пользователе записана
в файл журнала.
</body>
В результате
мы пришли к тому, что теперь все документы с расширениями html и htm рассматриваются как сценарии на PHP. Они запускаются уже после того, как подключен библиотекарь, так что могут пользоваться функцией Uses().
Если вы не собираетесь использовать библиотекарь, а хотите применять описанный выше механизм только для того, чтобы включить PHP для файлов с расширением html, лучше прочитайте конец этой главы. Там описано, как сделать это проще.