
Самоучитель по PHP 4
.pdf
Глава 33. Разные советы |
501 |
выполнить и загрузить этот сценарий с сервера. Звучит, как языческое заклинание, не правда ли? Пожалуй, с первого взгляда не совсем ясно, зачем же может понадобиться эта хваленая самопереадресация в Web-программировании.
Рассмотрим пример. Предположим, у нас имеется сценарий — гостевая книга наподобие той, эскиз которой мы рассматривали в главе 30. С точки зрения пользователя сценарий представляет собой страницу с адресом http://www.ourserver.ru/book/index.html. Если набрать этот адрес в браузе-
ре, появится, во-первых, форма с предложением добавить новое сообщение в книгу, а во-вторых, список ранее добавленных "посланий". В атрибуте action тэга <form> указан адрес той же самой страницы index.html (это вписывается в трехуровневую схему разработки сценариев), поэтому после набора сообщения и нажатия на кнопку отправки фактически снова загружается та же самая страница. Только перед ее загрузкой генератор данных гостевой книги определяет, что необходимо добавить новую запись, и делает это.
В общем-то, довольно стандартная схема. Пусть пользователь набрал свое послание и отправил его на сервер. Перед ним появится список сообщений, первым из которых будет его собственное. Пока вроде бы все верно. И теперь пользователь, ничего не подозревая, нажимает на кнопку Обновить в браузере, заставляя последний, как он думает, перезагрузить страницу гостевой книги.
Но в действительности происходит совсем не то, что он ожидает. Если данные формы были посланы методом POST, браузер выведет на экран диалоговое окно запроса примерно такого содержания: "Вы пытаетесь обновить данные страницы, которая была сгенерирована с применением метода POST. Повторить отправку данных (да или нет)?" Если пользователь нажмет кнопку Нет, то гостевая книга не перезагрузится, а появится совершенно бесполезная стандартная страница с сообщением о том, что "данные устарели". Если же он подтвердит вторичную отправку данных, его сообщение будет добавлено в книгу еще раз, а потому "размножится". Довольно нетрудно понять, почему так происходит: ведь браузер "не знает", что в действительности пользователь хочет лишь вторично "зайти" на адрес страницы книги, а не повторить отправку всех данных формы.
Однако ситуация становится еще плачевнее, если мы применяем в нашей гостевой книге метод GET. В этом случае при нажатии на кнопку Обновить браузер "без лишних разговоров" пошлет данные формы на сервер повторно, так что сообщение будет лишний раз добавлено в гостевую книгу без предупреждений. И это тоже понятно: ведь метод GET — не что иное, как простое изменение URL страницы, а именно, добавление в его конец символа ?, после которого следуют параметры (в том числе текст записи).
Впрочем, метод GET практически никогда не применяется в интерактивных сце- нариях, таких как гостевые книги, форумы и т. д. Мы уже говорили в первой части книги на эту тему, но она настолько важна, что я повторюсь. Если для

502 |
Часть V. Приемы программирования на PHP |
одних и тех же данных формы при их многократной отправке страница все- гда выглядит одинаково, значит, эти данные логично передавать методом GET. В противном случае необходимо применять метод POST. Такое поло-
жение вещей связано также и с тем, что некоторые proxy-серверы могут кэши- ровать страницы, полученные методом GET, но они никогда не кэшируют их при использовании POST.
Самопереадресация — это как раз то средство, которое позволяет разрешить рассмотренный конфликт в сторону пользователя. В самом деле, предположим, что при получении уведомления о новом сообщении генератор данных вставляет их в базу данных, а затем посылает браузеру заголовок, заставляющий его перезагрузить страницу гостевой книги. В этом случае страница уже не будет представлять собой результат работы метода POST, это будет обычный HTML-документ, загруженный с сервера, как будто бы пользователь считал файл только что самостоятельно и "вручную". Неудивительно, что кнопка браузера Обновить будет работать так, как ей и положено.
Впрочем, при использовании самопереадресации очень легко наткнуться на один неприятный "подводный камень". Это — ошибка некоторых версий браузера Netscape, заключающаяся в том, что любые страницы, полученные им в результате самопереадресации, он ошибочно принимает за пустые (и соответственно отображает). И все же выход есть: достаточно немного модифицировать URL страницы, чтобы браузер "подумал", что это уже другой документ, а не тот же самый. Листинг 33.3 показывает, как это можно сделать. В целях экономии места я разместил шаблон страницы и генератор данных в одном файле.
Листинг 33.3. Самопереадресация
<?
// Считываем содержимое базы данных. $Book=@Unserialize(join("",File("book.dat"))); if(!$Book) $Book=array();
// Проверяем, не нужно ли добавить запись...
if(@$Go) { array_unshift($Book,$Text); $f=fopen("book.dat","w"); fwrite($f,Serialize($Book)); fclose($f);
//Внимание! Самопереадресация. Обратите внимание на то,
//какой заголовок мы посылаем.
Header("Location: http://$HTTP_HOST$REQUEST_URI?".time()); exit; // Завершить сценарий.
}

Глава 33. Разные советы |
503 |
?>
<form action=sr.php method=post>
Введите текст:<br>
<input type=text name=Text><br>
<input type=submit name=Go value="Go!"> </form>
<?foreach($Book as $k=>$v) {?> <?=$v?>
<hr>
<?}?>
Мы обеспечиваем "уникальность" URL страницы гостевой книги за счет добавления в его конец текущего времени в секундах, прошедших с 1 января 1970 года (так называемый Unix timestamp). Вряд ли пользователь будет обновлять страницу чаще, чем раз в секунду, поэтому такой способ прекрасно подходит для наших целей.
Обратите внимание на то, что в заголовке Location мы передаем полный URL страницы, включая имя хоста. Большинство браузеров умеют "понимать" и сокращенные пути (например, без указания имени сервера), но некоторые — нет, так что лучше не искушать судьбу.
Запрет кэширования страниц
Изрядное количество сценариев генерируют страницы, которые постоянно изменяются во времени, поэтому кэширование таких документов, которое иногда пытаются провести "слишком умные" браузеры и proxy-серверы, следует отключить. В противном случае пользователь может увидеть устаревшие данные и не заметить, что ваша страница изменилась.
Вообще говоря, если браузер "захочет" сохранять страницу в кэше и затем постоянно выдавать пользователю одно и то же, никакая сила не сможет запретить ему делать это. К счастью, большинство браузеров более "послушны" — они адекватно реагируют на специальные заголовки запрета кэширования, которые могут присутствовать в странице, полученной с сервера. То же самое делают и proxy-серверы — правда, они используют уже другие заголовки.
В листинге 33.4 приведены четыре заголовка, которые необходимо послать вместе с телом страницы, чтобы браузеры и proxy-серверы не пытались ее кэшировать. Опыт подтверждает, что эти 4 заголовка — минимум. Если убрать хотя бы один из них, некоторые proxy-серверы (или браузеры) могут "не понять", что от них требуется.
Листинг 33.4. Заголовки для запрета кэширования
Header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); |
// Дата в прошлом |

504 Часть V. Приемы программирования на PHP
Header("Last-Modified: ".gmdate("D, d M Y H:i:s")."GMT"); // Изменилась
Header("Cache-Control: no-cache, must-revalidate"); |
// |
для |
HTTP/1.1 |
Header("Pragma: no-cache"); |
// |
для |
HTTP/1.0 |
Излишне напоминать, что все заголовки должны быть отправлены до первой команды вывода в сценарии.
При использовании шаблонизатора наподобие того, который был описан в главе 30, это требование является необязательным. В таком случае весь ре-
зультат работы сценария и шаблона буферизируется и не отправляется в браузер до самого последнего момента.
Несколько слов о флажках checkbox
Переключатель с независимым выбором (checkbox или более коротко — флажок) имеет одну довольно неприятную особенность, которая иногда может помешать Webпрограммисту. Вы, наверное, помните, что когда перед отправкой формы пользователь установил его в выбранное состояние, то сценарию в числе других параметров приходит пара имя_флажка=значение.
В то же время, если флажок не был установлен пользователем, указанная пара не посылается. Часто это бывает не совсем то, что нужно. Мы бы хотели, чтобы в невыбранном состоянии флажок также присылал данные, но только значение было равно какой-нибудь специальной величине — например, нулю или пустой строке.
К нашей радости, добиться этого эффекта в PHP довольно несложно. Достаточно воспользоваться одноименным скрытым полем (hidden) со значением, равным, например, нулю, разместив его перед нужным флажком. Вот пример:
Листинг 33.5. Гарантированная установка значений флажков
<? if(@$Go) {
foreach($Known as $k=>$v)
if($v) echo "Вы знаете язык $k!<br>"; else echo "Вы не знаете языка $k. <br>";
}
?>
<form action=lang.php method=post>
Какие языки программирования вы знаете?<br> <input type=hidden name=Known[PHP] value=0>
<input type=checkbox name= Known[PHP] value=1>PHP<br>

Глава 33. Разные советы |
505 |
<input type=hidden name=Known[Perl] value=0>
<input type=checkbox name= Known[Perl] value=1>PHP<br> <input type=submit name=Go value="Go!">
</form>
Теперь в случае, если пользователь не выберет какой-нибудь из флажков, браузер отправит сценарию пару Known[язык]=0, сгенерированную соответствующим скрытым полем, и в массиве $Known создастся соответствующий элемент. Если пользователь выбрал флажок, эта пара также будет послана, но сразу же после нее последует пара Known[язык]=1, которая "перекроет" предыдущее значение.
Не включи мы скрытые поля в форму из листинга 33.5, сценарий печатал бы только сообщения о тех языках, которые "знает пользователь", пропуская языки, ему "неизвестные". В нашем же случае сценарий реагирует и на неустановленные флажки.
Такой способ немного увеличивает объем данных, передаваемых методом POST, за счет тех самых пар, которые генерируются скрытыми полями. Впро- чем, в реальной жизни это "увеличение" практически незаметно (особенно для
POST-форм).

ЧАСТЬ VI.
ПРИЛОЖЕНИЯ

Приложение 1
Файл конфигурации
Apache httpd.conf
Это приложение содержит полный текст файла конфигурации сервера Apache httpd.conf с комментариями на русском языке.
Содержимое листинга П1.1 полностью соответствует указаниям по настройке Apache, приведенным в части II книги. Если у вас по какой-то причине не получит- ся правильно установить Apache и PHP версии 4, руководствуясь этими указания- ми, представленный ниже текст файла httpd.conf решит все проблемы.
Несколько слов о формате httpd.conf. Файл состоит из строк, содержащих дирек- тивы Apache. В одной строке может быть расположено не более одной директивы. Текст от # äо конца строки считается комментарием и не берется в рассмотрение. Также игнорируются пустые строки.
При изменении начальной конфигурации файла возможно группирование нескольких директив в блоки, или контейнеры. При этом Apache поддерживает только ограниченное количество допустимых типов контейнеров. Любой блок-контейнер начинается строкой вида <ИмяКонтейнера>, расположенной, как обычно, на отдельной строке, и завершается тэгом </ИмяКонтейнера>. Некоторые (но не все) блоки могут быть вложенными.
Директивы, касающиеся индивидуальных настроек для каталогов или файлов, могут также помещаться в специальные файлы .htaccess, расположенные в соответствующих местах дерева каталогов сайта. Эти файлы должны иметь тот же формат, что и httpd.conf. Однако для них имеются особые ограничения на использование директив и блоков — список недопустимых можно найти в документации, поставляе-
мой с Apache.
Листинг П1.1. Файл конфигурации Apache httpd.conf
#Основан на конфигурационных файлах сервера NSCA, созданных
#Робом МакКулом.
#
# Главный файл конфигурации сервера Apache, содержащий директивы,
510 |
Часть VI. Приложения |
#управляющие работой сервера. За более детальной информацией
#обращайтесь по адресу http://www.apache.org/docs/.
#
#Не стоит читать эти директивы без понимания их роли. Они
#приведены здесь лишь в качестве примера одного из возможных
#вариантов. В случае сомнений обращайтесь к сопроводительной
#документации. Считайте, что вас предупредили.
#
#После просмотра и анализа файла httpd.conf сервер
#попробует найти и обработать файлы:
#C:/Program Files/Apache Group/Apache/conf/srm.conf, а затем
#C:/Program Files/Apache Group/Apache/conf/access.conf,
#если вы не переопределили эти имена директивами ResourceConfig
#и/или AccessConfig.
#
#Директивы конфигурации сгруппированы в три основных раздела:
#1. Директивы, управляющие процессом Apache в целом (глобальное
#окружение).
#2. Директивы, определяющие параметры "главного" сервера, или
#сервера "по умолчанию", отвечающего на запросы, которые
#не обрабатываются виртуальными хостами. Эти директивы задают
#также установки по умолчанию для всех остальных виртуальных хостов.
#3. Установки для виртуальных хостов, позволяющие обрабатывать
#запросы Web одним-единственным сервером Apache, но направлять
#по раздельным IP-адресам или именам хостов.
#
#Файлы конфигурации программы и журналы регистрации событий
#(в программисткой среде они чаще называются "конфигами" и "логами",
#так что, я думаю, ничего страшного не произойдет, если я буду
#придерживаться этой терминологии и здесь).
#Если имена файлов, определенных вами для управления сервером,
#начинаются с символа / (или "диск:/" для Win32), сервер будет
#использовать явно указанный в этом имени полный путь. Если же имена не
#начинаются с "/", то для определения пути будет задействовано значение
#директивы ServerRoot. Так, logs/foo.log при значении ServerRoot,
#равном /usr/local/apache, будет интерпретироваться сервером как
#/usr/local/apache/logs/foo.log.
#