Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Самоучитель по PHP 4

.pdf
Скачиваний:
82
Добавлен:
02.05.2014
Размер:
4.36 Mб
Скачать

Глава 3. CGI изнутри

61

Кнопка сброса формы (reset)

<input type=reset value=текст_кнопки

>

Пожалуй, это самый простой элемент формы. Тэг создает кнопку, при нажатии на которую все элементы формы в браузере будут сброшены (точнее, установлены в то состояние, которое было задано в их атрибутах по умолчанию). Причем отправка формы не производится, т. е. для сценария кнопка reset незаметна.

Рисунок для отправки формы (image)

<input type=image [name=имя] src=изображение

>

Создает рисунок, при щелчке на котором кнопкой мыши будет происходить то же, что и при нажатии на кнопку submit, за тем исключением, что сценарию также будут пересланы координаты в пикселах того места, где произведен щелчок (отсчитываемые от левого верхнего угла рисунка). Придут они в форме: имя.x=X&имя.y=Y, где (X, Y) — координаты точки. Если же атрибут name не задан, то координаты поступят в формате: x=X&y=Y.

Тэг <textarea> — многострочное поле ввода текста

Теперь посмотрим, что же из себя представляет тэг <textarea>. Смысл у него тот же, что и у <input type=text>, разве что может быть отправлена не одна строка текста, а сразу несколько. Формат тэга следующий:

<textarea name=имя

[width=ширина][height=высота] [wrap=тип]

>Текст, который будет изначально отображен в текстовом поле</textarea>

Как легко видеть, этот тэг имеет закрывающий парный. Параметр width задает ширину поля ввода в символах, а height — его высоту. Параметр wrap определяет, как будет выглядеть текст в поле ввода. Он может иметь одно из трех значений (по умолчанию подразумевается none).

rVirtual — наиболее удобный тип вывода. Справа от текстового поля выводится полоса прокрутки, и текст, который набирает пользователь, внешне выглядит разбитым на строки в соответствии с шириной поля ввода, причем перенос осуществляется по словам. Однако символ новой строки вставляется в текст только при нажатии <Enter>.

62

Часть I. Основы Web-программирования

rPhysical — зависит от реализации браузера, обычно очень похож на none.

rNone — текст отображается в том виде, в котором заносится. Если он не умещается в текстовое поле, активизируются линейки прокрутки (в том числе, и горизонтальная).

После отправки формы текст, который ввел пользователь, будет, как обычно, представлен парой имя=текст, аналогично тэгу однострочного поля ввода

<input type=text>.

Тэг <select> — список

У нас остался последний тэг — <select>. Он представляет собой выпадающий (или раскрытый) список. Одновременно могут быть выбрана одна или несколько строк. Формат этого тэга следующий:

<select name=имя [size=размер] [multiple]>

<option [value1=значение1][selected]>Строка1</option> <option [value2=значение2][selected]>Строка2</option>

. . .

<option [valueN=значениеN][selected]>СтрокаN</option> </select>

Мы видим, что и этот тэг имеет парный закрывающий. Кроме того, его существование немыслимо без тэгов <option>, которые и определяют содержимое списка.

Параметр size задает, сколько строк будет занимать список. Если size равен 1, то список будет выпадающим, в противном случае — занимает size строк и имеет полосы прокрутки. Если указан атрибут multiple, то будет разрешено выбирать сразу несколько элементов из списка, а иначе — только один. Кроме того, атрибут multiple не имеет смысла для выпадающего списка.

Каждая строка списка определяется своим тэгом <option>. Если в нем задан атрибут value, как это часто бывает, то соответствующая строка списка будет идентифицироваться его значением, а если не задан, то самим текстом этой строки (считается, что value равно самой строке). Кроме того, если указан параметр selected, то данная строка будет изначально выбранной. Кстати, чуть не забыл: закрывающие тэги </option> можно опускать, если упрощение не создает конфликтов с синтаксисом HTML (в действительности это можно делать почти всегда).

Давайте теперь посмотрим, в какой форме пересылаются данные списка сценарию. Ну, со списком одиночного выбора вроде бы ясно — просто передается пара имя=значение, где имя — имя тэга <select>, а значение — идентификатор выбранного элемента (то есть, либо атрибут value, либо сама строка элемента списка).

Глава 3. CGI изнутри

63

Списки множественного выбора (multiple)

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

имя=значение1&имя=значение2&...&имя=значениеN

Кстати говоря, совершенно не уникальный случай — то, что с одним именем связано сразу несколько значений. Действительно, нам никто не мешает создавать и другие тэги с идентичными именами. Это часто делается, например, для переключателейфлажков:

<input type=checkbox name=имя value="Один">Один<br> <input type=checkbox name=имя value="Два">Два<br> <input type=checkbox name=имя value="Три">Три<br>

Если теперь пользователь установит сразу все флажки, то сценарию поступит строка (конечно, в URL-кодированном виде):

имя=Один&имя=Два&имя=Три

Из всего сказанного следует не очень утешительный вывод: при разборе строки параметров в сценарии мы не можем полагаться на то, что каждой переменной соответствует только одно значение. Нам придется учитывать, что их может быть не "один", а "много". А это очень неприятно с точки зрения программирования — особенно на Си.

Попутно мы обнаружили, что любой multiple-список может быть представлен набором флажков (независимых переключателей), а любой не-miltiple — в виде нескольких радиокнопок. Так что, вообще говоря, тэг <select> — некоторое функциональное излишество, и с точки зрения сценария вполне может заменяться флажками и радиокнопками.

Загрузка файлов

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

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

64

Часть I. Основы Web-программирования

вателя на сервер. Для этого в языке HTML предусмотрены специальные средства. Рассмотрим их подробнее.

Формат данных

В свое время я говорил, что все данные из формы при передаче их на сервер упаковываются в строку при помощи символов ?, & и =. Легко видеть, что при загрузке файлов такой способ, хотя и приемлем, но будет существенно увеличивать размер передаваемой информации. Действительно, ведь большинство файлов — бинарные, а мы знаем, что при URL-кодировании данные таких файлов сильно "распухают" — примерно в три раза (например, простой нулевой байт при URL-кодировании превратится в %00). Это сильно замедлит передачу и увеличит нагрузку на канал. И вот, отчасти специально для решения указанной проблемы был изобретен другой формат передачи данных, отличный от того, который мы до сих пор рассматривали. В нем уже не используются пресловутые символы ? и &. Кроме того, похоже, в случае применения такого формата передачи может быть задействован только метод POST, но не метод GET. Нас это вполне устроит — ведь файлы обычно большие, и доставлять их через GET вряд ли разумно...

Если нужно указать браузеру, что в какой-то форме следует применять другой формат передачи, следует в соответствующем тэге <form> задать атрибут enctype=multipart/form-data. (Кстати говоря, если этот атрибут не указан, то форма считается обычной, что эквивалентно enctype=application/x-www-form- urlencoded — именно так обозначается привычный нам формат передачи.) После этого данные, поступившие от нашей формы, будут выглядеть как несколько блоков информации (по одному на элемент формы). Каждый такой блок очень напоминает HTTP-формат "заголовки-данные", используемый при традиционном формате передачи. Выглядит блок примерно так (\n, как всегда, обозначает символ перевода строки):

-----------------Идентификатор_начала\n Content-Disposition: form-data; name="имя"\n \n

значение\n

Например, пусть у нас есть форма:

Листинг 3.7. Multipart-форма

<form action=... enctype=multipart/form-data method=post> Name: <input type=text name="Name" value="Мое имя"><br> Box: <input type=checkbox name="Box" value=1 checked><br>

Area: <input type=textarea name="Area">Это какой-то текст</textarea><br> <input type=submit>

Глава 3. CGI изнутри

65

</form>

 

Данные, поступившие по нажатии кнопки submit на сервер, будут иметь

следующий вид:

 

----------------127462537625367\n

 

Content-Disposition: form-data; name="Name"\n

 

\n

 

Мое имя\n

 

----------------127462537625367\n

 

Content-Disposition: form-data; name="Box"\n

 

\n

 

1\n

 

----------------127462537625367\n

 

Content-Disposition: form-data; name="Area"\n

 

\n

 

Это какой-то текст\n

 

Заметьте, что несколько дефисов и число (которое мы ранее назвали Идентификатор_начала) предшествуют каждому блоку. Более того, строка из дефисов и этого числа служит своеобразным маркером, который разделяет блоки. Очевидно, эта строка должна быть уникальной во всех данных. Именно так ее и формирует браузер. Правда, сказанное означает, что сегодня идентификатор будет одним, а завтра, возможно, совсем другим. Так что нам придется, прежде чем анализировать данные, считать этот идентификатор в буфер (им будет последовательность символов до первого символа \n).

Стандарт протокола HTTP говорит нам, что идентификатор начала также дол- жен быть доступен через одну из переменных окружения. Но я не помню и не хочу знать ее название сейчас объясню, почему. Некоторые браузеры (осо- бенно старые) путают этот идентификатор и присылают его неправильно с двумя предшествующими минусами (а остальные без них), так что сцена- рии, не рассчитывающие на такой подвох, перестанут работать. Никогда не полагайтесь на эту переменную окружения (даже если узнаете, как она назы- вается)! Вместо этого читайте последовательность символов до первого пере- вода строки и воспринимайте именно ее как разделитель.

Далее алгоритм разбора должен быть следующим: в цикле мы пропускаем символы идентификатора и перевода строки, извлекаем подстроку имя="что-то" (не обращая внимания на Content-Disposition), дожидаемся двух символов перевода строки и затем считаем значением соответствующего поля все те данные, которые размещены до строки \nИдентификатор (или же до конца, если такой строки больше нет). Как видите, все довольно просто.

66

Часть I. Основы Web-программирования

Стандарт HTTP предписывает, чтобы перевод строки содержал два символа \r\n, а не один \n. Как вы уже, наверное, чувствуете, существуют браузеры, которые об этом и не догадываются и посылают только один \n. Так что, будь- те готовы к тому, чтобы правильно обрабатывать и эту ситуацию.

Тэг загрузки файла (file)

Теперь вернемся к тому, с чего начали — к загрузке файлов. Сначала выясним, какой тэг надо вставить в форму, чтобы в ней появился соответствующий элемент управления — поле ввода текста с кнопкой Browse справа. Таким тэгом является разновид-

ность <input>:

<input type=file name=имя_элемента

[value=имя_файла]

>

Пусть пользователь выбрал какой-то файл (скажем, с именем каталог\ имя_файла) и нажал кнопку отправки. В этом случае для нашего элемента формы создается один блок примерно такого вида:

----------------127462537625367\n

Content-Disposition: form-data; name="имя_элемента"; Ä filename="каталог\имя_файла"\n \n

........

Бинарные данные этого файла любой длины. Здесь могут быть совершенно любые байты без всякого ограничения.

........

\n

Мы видим, что сценарию вместе с содержимым файла передается и его имя в системе пользователя (параметр filename).

На этом, пожалуй, и завершим обозрение возможностей загрузки файлов.

Надеюсь, я посеял в вас неприязненное отношение к подобным методам: действительно, программировать это — не самое приятное занятие на свете (укажу только на то, что придется использовать приемы программной буферизации, чтобы правильно найти разделитель). Вот еще один довод в пользу PHP, в котором не нужно выполнять в принципе никакой работы, чтобы создать полноценный сценарий с возможностью загрузки файла.

Глава 3. CGI изнутри

67

Что такое Cookies и с чем их едят

Сначала хотелось бы сказать пару слов насчет самого термина Cookies (это множественное число, произносится как "кукис" или, более "русифицировано", "куки"). В буквальном переводе слово звучит как "печенье", и почему компания Netscape так назвала свое изобретение, не совсем ясно. А поскольку писать "печенье" несколько неудобно, чтобы не вызывать несвоевременных гастрономических ассоциаций, везде, где можно, я буду применять именно слово Cookies, с большой буквы, во множественном числе и мужского рода. Кстати, в единственном числе это понятие записывается Cookie и произносится на русский манер — "кука".

Начну с примера. Скажем, мы хотим завести гостевую книгу: пользователь вводит свое имя, E-mail, адрес домашней странички (и другую информацию о себе), наконец, текст сообщения, и после нажатия на кнопку его мысль отправляется в путешествие по проводам и серверам, чтобы в конце концов попасть в некую базу данных на нашем сервере и остаться там на веки вечные. М-да….

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

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

Первый способ: хранить на сервере отдельную базу данных, в которой для каждого пользователя по его IP-адресу можно было бы получить последние им же введенные данные. В принципе, это решение довольно универсально, однако у него есть два существенных недостатка, которые сводят на нет все преимущества. Главный из них — то, что большинство пользователей не имеют фиксированного (как говорят, стати- ческого) IP-адреса — каждый раз при входе в Интернет он назначается им (провайдером) автоматически (сервер провайдера обычно имеет контроль над несколькими десятками зарезервированных IP-адресов, доступных для пользователя, и выбирает для него тот, который еще не занят кем-то еще). Таким образом, мы вряд ли сможем определить, кто на самом деле зашел в нашу гостевую книгу. Второй недостаток мало связан с первым — дело в том, что если ваших пользователей очень много, то довольно проблематично в принципе иметь такую базу данных, ведь она занимает место на диске, не говоря уж о издержках на поиск в ней.

Второй способ подразумевает использование Cookies. Cookie — это небольшая именованная порция информации, которая хранится в каталоге браузера пользователя (а не на сервере, заметьте!), но которую сервер (а точнее, сценарий) волен в любой момент изменить. Кстати, сценарий также получает все Cookies, которые сохранены на удаленном компьютере, при каждом своем запуске, так что он может в любой момент

68

Часть I. Основы Web-программирования

времени узнать, что же там у пользователя установлено. Самым удобным в Cookies является то, что они могут храниться недели и годы до тех пор, пока их не обновит сервер или же пока не истечет срок их жизни (который тоже назначается сценарием при создании Cookie). Таким образом, мы можем иметь Cookies, которые "живут" всего несколько минут (или до того момента, пока не закроют браузер), а можем — "долгожителей".

Не правда ли, последний способ представляет собой идеальное решение для нашей проблемы? Действительно, теперь сцеанарию гостевой книги достаточно получить у пользователя его данные, запомнить их в Cookies (как это сделать — см. ниже), а затем работать, будто ничего и не произошло. Конечно, перед выводом HTMLдокумента формы обязательно придется проставить значения value для некоторых элементов (которые, ясно, извлечены из соответствующих Cookies).

Но не все так гладко. Конечно, и у этой схемы есть недостатки. Первый из них — не все браузеры поддерживают Cookies, а пользователи тех, которые поддерживают, иногда имеют обыкновение отключать Cookies — якобы для большей безопасности (хотя безопасность тут совсем ни при чем, дело в самих этих пользователях). Второй недостаток заключается в том, что каждый браузер хранит свои Cookies отдельно. То есть Cookies, установленные при пользовании Internet Explorer, не будут "видны" при работе в Netscape, и наоборот.

Но, согласитесь, все же это почти не умаляет достоинств Cookies — в конце концов, обычно пользователи работают только в одном из перечисленных браузеров. Кстати, все чаще в Internet Explorer. На момент написания этих строк указанный браузер имеет в несколько раз большие возможности, чем Netscape (работая при этом, правда, несколько медленнее). Что ж... Время покажет, кто из них выживет.

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

Зачем нужны имя сервера и каталог? Очень просто: дело в том, что сценарию передаются только те Cookies, у которых параметры с именем сервера и каталога совпадают соответственно с хостом и каталогом сценария (ну, на самом деле каталог не должен совпадать полностью, он может являться подкаталогом того, который создан для хранения Cookies). Так что совершенно невозможно получить доступ к "чужим" Cookies — браузер просто не будет посылать их серверу. Это и понятно: представьте себе, сколько ненужной информации передавалось бы сценарию, если бы все было не так (особенно если пользователь довольно активно посещает различные серверы, которые не прочь поставить ему свой набор Cookies). Кроме того, дополнительные сведения предоставляются в целях защиты информации от несанкционированного доступа — ведь в каком-то Cookie может храниться, скажем, важный пароль (как часто делается при авторизации), а он должен быть доступен только одному определенному хосту.

Глава 3. CGI изнутри

69

Установка Cookie

Мы подошли к вопросу: как же сценарий может установить Cookie в браузере пользователя? Ведь он работает "на одном конце провода", а пользователь — на другом. Решение довольно логично: команда установки Cookie — это просто один из заголовков ответа, передаваемых сервером браузеру. То есть, перед тем как выводить Content-type, мы можем указать некоторые команды для установки Cookie. Выглядит такая команда следующим образом (разумеется, как и всякий заголовок, записывается она в одну строку):

Set-Cookie: name=value; expires=дата; domain=имя_хоста; path=путь; secure

Существует и другой подход активизировать Cookie — при помощи HTML-тэга <meta>. Соответственно, как только браузер увидит такой тэг, он займется обработкой Cookie. Формат тэга такой:

<meta http-equiv="Set-Cookie"

content="name=value; expires=дата; domain=имя_хоста; path=путь; secure"

>

Мы можем видеть, что даже названия параметров в этих двух способах одинаковы. Какой из них выбрать — решать вам: если все заголовки уже выведены к тому моменту, когда вам потребовалось установить Cookie, используйте тэг <meta>. В противном случае лучше взять на вооружение заголовки, т. к. они не видны пользователю, а чем пользователь меньше видит при просмотре исходного текста страницы в браузере — тем лучше нам, программистам.

Возможно, вы спросите, нахмурив брови: "Что же, с точки зрения программиста хороший пользователь слепой пользователь?" Тогда я отвечу: "Что вы, нет и еще раз нет! Такой пользователь хорош лишь для дизайнера, для програм- миста же желателен пользователь безрукий (или, по крайней мере, лишенный клавиатуры и мыши)".

Вот что означают параметры Cookie:

name

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

value

Текст, который будет рассматриваться как значение Cookie. Важно отметить, что этот текст (ровно как и строка названия Cookie) должен быть URL-кодирован. Таким об-

70

Часть I. Основы Web-программирования

разом, я должен отметить неприятный факт, что придется писать еще и функцию URL-кодирования (которая, кстати, раза в 2 сложнее, чем функция для декодирования, т. к. требует дополнительного выделения памяти).

expires

Необязательная пара expires=дата задает время жизни нашего Cookie. Точнее, Cookie самоуничтожится, как только наступит указанная дата. Например, если задать expires=Friday,31-Dec-99 23:59:59 GMT, то "печенье" будет "жить" только до

31 декабря 1999 года. Кстати, вот вам и вторая неприятность: хорошо, если мы знаем наверняка время "смерти" Cookie. А если нам нужно его вычислять на основе текущего времени (например, если мы хотим, чтобы Cookie существовал 10 дней после его установки, как в подавляющем большинстве случаев и происходит)? Придется использовать функцию, которая формировала бы календарную дату в указанном выше формате. Кстати, если этот параметр не указан, то временем жизни будет считаться вся текущая сессия работы браузера, до того момента, как пользователь его закроет.

domain

Параметр domain=имя_хоста задает имя хоста, с которого установили Cookie. Ранее я уже говорил про этот параметр. Так вот, оказывается, его можно менять вручную, прописав здесь нужный адрес, и таким образом "подарить" Cookie другому хосту. Только в том случае, если параметр не задан, имя хоста определяется браузером автоматически.

path

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

secure

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

После запуска сценария, выводящего соответствующий заголовок (или тэг <meta>), у пользователя появится Cookie с именем name и значением value. Еще раз напоминаю: значения всех параметров Cookie должны быть URL-кодированы, в противном случае возможны неожиданности.