Учебник по CGI

Язык PHP — сравнительно молодой,
но в то же время удивительно удобный и гибкий язык для программирования Web. С
помощью него можно написать 99% программ, которые обычно требуются в Интер-
нете. Для оставшегося 1% придется использовать Си или Perl (или другой универ-
сальный язык).

1. CGI-интерфейс.

1.1. Заголовки и метод GET

Задумаемся на минуту, что же происходит, когда мы набираем в браузере строку
somestring и нажимаем <Enter>. Браузер посылает серверу запрос somestring?
Нет, конечно. Все немного сложнее. Он анализирует строку, выделяет из нее имя сер-
вера и порт (а также имя протокола, но нам это сейчас не интересно), устанавливает
соединение с Web-сервером по адресу сервер:порт и посылает ему что-то типа сле-
дующего:
GET somestring HTTP/1.0\n
...другая информация...
\n\n
Здесь \n означает символ перевода строки, а \n\n — два обязательных символа но-
вой строки, которые являются маркером окончания запроса (точнее, окончания заго-
ловков запроса). Пока мы не пошлем этот маркер, сервер не будет обрабатывать наш
запрос.
Как видим, после GET-строки могут следовать и другие строки с информацией, раз-
деленные символом перевода строки. Их обычно формирует браузер. Такие строки
называются заголовками (headers), и их может быть сколько угодно. Протокол HTTP
как раз и задает правила формирования и интерпретации этих заголовков.
Вот мы и начинаем знакомство с протоколом HTTP. Как видите, он представляет со-
бой ни что иное, как просто набор заголовков, которыми обмениваются сервер и
браузер, и еще пару соглашений насчет метода POST, которые мы вскоре рассмотрим.
Не все заголовки обрабатываются сервером — некоторые просто пересылаются за-
пускаемому сценарию с помощью переменных окружения. Переменные окружения
представляют собой именованные значения параметров, которые операционная сис-
тема (точнее, процесс-родитель) передает запущенной программе. Программа может
с помощью специальных функций (их мы рассмотрим в следующей главе на приме-
рах) получить значение любой установленной переменной окружения, указав ее имя.
Именно так и должен поступать CGI-сценарий, когда захочет узнать значение того
или иного заголовка запроса. К сожалению, набор передаваемых сценарию заголов-
ков ограничен стандартами, и некоторые заголовки нельзя получить из сценария ни-
каким способом (ему просто недоступна соответствующая переменная окружения).
Такие случаи мы будем оговаривать особо.

GET
- Формат: GET сценарий?параметры HTTP/1.0
- Переменные окружения: REQUEST_URI; в переменной QUERY_STRING сохраняется
значение параметры, в переменной REQUEST_METHOD — ключевое слово GET.

POST
- Формат: POST сценарий?параметры HTTP/1.0
- Переменная окружения: REQUEST_URI; в переменной QUERY_STRING сохраняется
значение параметры, в переменной REQUEST_METHOD — слово POST.
Этот заголовок используется при передаче данных методом POST. Вскоре мы рас-
смотрим этот метод подробнее, а пока скажу лишь, что он отличается от метода GET
тем, что данные можно передавать не только через командную строку, но и в конце
всех заголовков.

Content-type
- Формат: Content-Type: application/x-www-form-urlencoded
- Переменная: CONTENT_TYPE
Данный заголовок идентифицирует тип передаваемых данных. Обычно для этого ука-
зывается значение application/x-www-form-urlencoded, что означает формат, в
котором все управляющие символы (отличные от алфавитно-цифровых и других ото-
бражаемых) специальным образом кодируются. Это тот самый формат передачи, ко-
торый используется методами GET и POST. Довольно распространен и другой формат,
и называется он multipart/form-data. Мы разберем его, когда будем обсуждать
вопрос, касающийся загрузки файлов на сервер.
Хочу обратить ваше внимание на то, что сервер никак не интерпретирует рассматри-
ваемый заголовок, а просто передает его сценарию через переменную окружения.

User-Agent
- Формат: User-Agent: Mozilla/4.5 [en] (Win95; I)
- Переменная окружения: HTTP_USER_AGENT
Уточняет версию браузера (в данном случае это Netscape Navigator).

Referer
- Формат: Referer: URL_адрес
- Переменная окружения: HTTP_REFERER
Как правило, этот заголовок формируется браузером и содержит URL страницы, с
которой осуществился переход на текущую страницу по гиперссылке. Впрочем, если
вы пишете сценарий, который в целях безопасности отслеживает значение данного
заголовка (например, для его запуска только с определенной страницы), помните, что
умелый хакер всегда сможет подделать заголовок Referer.

Content-length
- Формат: Content-length: длина
- Переменная окружения: CONTENT_LENGTH
Заголовок содержит строку, являющуюся десятичным представлением длины данных
в байтах, передаваемых методом POST. Если задействуется метод GET, то этот заго-
ловок отсутствует, и значит, переменная окружения не устанавливается.

Cookie
- Формат: Cookie: значения_Cookies
- Переменная окружения: HTTP_COOKIE
Здесь хранятся все Cookies в URL-кодировке (о Cookies мы подробнее поговорим в
следующей главе).

Accept
- Формат: Accept: text/html, text/plain, image/gif, image/jpeg
- Переменная окружения: HTTP_ACCEPT
В этом заголовке браузер перечисляет, какие типы документов он "понимает". Пере-
числение идет через запятую. К сожалению, в последнее время браузеры стали не-
сколько небрежны и часто присылают в этом заголовке значение */*, что обозначает
любой тип.
Существует еще множество заголовков запроса (часть из них востребуются только
протоколом HTTP 1.1), но мы не будем на них задерживаться.

1.2. Метод POST

Мы подошли к сути метода POST. А что, если мы в предыдущем примере зададим
вместо GET слово POST и после последнего заголовка (маркера \n\n) начнем переда-
вать какие-то данные? В этом случае сервер их воспримет и также передаст сцена-
рию. Только нужно не забыть проставить заголовок Content-length в соответствии
с размером данных, например:
POST /script.cgi HTTP/1.0\n
Content-length: 5\n
\n
Test!

Сервер начнет обработку запроса, не дожидаясь передачи данных после маркера кон-
ца заголовков. Иными словами, сценарий запустится сразу же после отправки \n\n, а
уж ждать или не ждать, пока придет строка Test! длиной 5 байтов — его дело.
Последнее означает, что сервер никак не интерпретирует POST-данные (точно так же,
как он не интерпретирует некоторые заголовки), а пересылает их непосредственно
сценарию. Но как же сценарий узнает, когда данные кончаются, т. е. когда ему пре-
кращать чтение информации, поступившей от браузера? В этом ему поможет пере-
менная окружения Content-Length, и именно на нее следует ориентироваться. Чуть
позже мы рассмотрим этот механизм подробнее.
Зачем нужен метод POST? В основном для того, чтобы передавать большие объемы
данных. Например, при загрузке файлов через Web (см. ниже) или при обработке
больших форм. Кроме того, метод POST часто используют для эстетических целей:
дело в том, что при применении GET, как вы, наверное, уже заметили, URL сценария
становится довольно длинным и неизящным, а POST-запрос оставляет URL без изме-
нения.

2. CGI изнутри

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

2.1. Передача документа пользователю

Вначале рассмотрим более простой вопрос: как программа посылает свой ответ (то
есть документ) пользователю.
А сделано это просто и логично (а главное, универсально и переносимо между опера-
ционными системами): сценарий просто помещает документ в стандартный поток
вывода (на Си он называется stdout), который находится под контролем программ-
ного обеспечения сервера. Иными словами, программа работает так, как будто нет
никакого пользователя, а нужно вывести текст прямо на "экран". (Это она так думает,
на самом деле выводимая информация будет перенаправлена сервером в браузер
пользователя. Ясно, что у сценария никакого "экрана" нет и быть не может.)
Ответ программы,

как и запрос пользователя, должен состоять из заголовков. Иными
словами, мы не можем просто направить документ в стандартный поток вывода: нам
сначала нужно по крайней мере указать, в каком формате информация должна быть
передана пользователю. Действительно, представьте, что произойдет, если браузер
попытается отобразить GIF-рисунок в текстовом виде? В худшем случае вашим поль-
зователям придется всю жизнь лечиться от заикания — особенно если до этого их
просили ввести номер кредитной карточки.…
Заголовки ответа
Заголовки ответа должны следовать точно в таком же формате, как и заголовки за-
проса, рассмотренные нами в предыдущей главе. А именно, это набор строк (завер-
шающийся пустой строкой), каждая из которых представляет собой имя заголовка и
его значение, разделенные двоеточием. Наличие пустого заголовка в конце также
можно интерпретировать как два стоящих подряд обозначения \n\n. Затем, как
обычно, могут следовать данные ответа, которые и являются документом, который
будет отображен браузером.

2.2. Заголовок кода ответа

Однако здесь все же имеется одно отличие от формата, который используется в заго-
ловках запроса. Дело в том, что первый заголовок ответа обязан иметь слегка специ-
фичный вид — в нем не должно быть двоеточия. Он задает так называемый код от-
вета сервера и выглядит, например, так:
HTTP/1.1 OK
или так:
HTTP/1.1 404 File Not Found
В первом примере заголовок говорит браузеру, что все в порядке и дальше следует
некоторый документ. Во втором примере сообщается, что затребованный файл не был
найден на сервере. Конечно, существует еще множество других кодов ошибок, но для
нас они не представляют особого интереса, и вот почему.
Чаще всего (за исключением редких случаев) браузеры не обращают особого внима-
ния на заголовок кода ответа, а просто выводят следующий за ним документ. Кроме
того, такой заголовок формируется сервером, а в сценарии мы никак не можем его
изменить (правда, есть специальный заголовок Status, но мы не будем здесь о нем
говорить). Поэтому я и не рассматриваю подробно этот вопрос в данной книге.
Вот другие наиболее распространенные заголовки ответа.

Content-type

- Формат: Content-type: mime_тип; charset=koi8-r
Задает тип документа и его кодировку. Параметр charset задает кодировку доку-
мента (в нашем примере это KOI8-R). Поле mime_тип определяет тип информации,
которую содержит документ:
- text/html — HTML-документ;
- text/plain — простой текстовый файл;
- image/gif — GIF-изображение;
- image/jpeg — JPG-изображение;
- еще несколько десятков других типов.

Pragma

Формат: Pragma: no-cache
Запрещает кэширование документа браузером, так что при повторном визите на
страницу браузер гарантированно загрузит ее снова, а не извлечет из своего кэша.
Это может быть полезно, если страница содержит, например, динамический счетчик
посещений.
Заголовок Pragma используется также и для других целей (и соответственно, после
двоеточия находятся другие значения строки), но мы не будем их здесь рассматри-
вать.

Location

Формат: Location: http://www.otherhost.com/somepage.html
Этот заголовок особенный и определяет, что браузер пользователя должен немедлен-
но перейти по указанному адресу, не дожидаясь тела документа ответа (как будто бы
пользователь сам набрал в адресной строке нужный URL). Так что, очевидно, если вы
собираетесь использовать заголовок Location, то никакого документа выводить не
надо.

Set-cookie

Формат: Set-cookie: параметры_cookie
Устанавливает Cookie в браузер пользователя. Позже в этой главе мы рассмотрим
подробнее, что такое Cookies и как с ними работать.

Date

Формат: Date: Sat, 08 Jan 2000 11:56:26 GMT
Указывает браузеру дату отправки документа.

Server

Формат: Server: Apache/1.3.9 (Unix) PHP/3.0.12
Устанавливается сервером и указывает браузеру тип сервера и другую информацию о
серверном программном обеспечении.

2.3. Передача информации CGI-сценарию

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

2.3.1. Переменные окружения

HTTP_ACCEPT
В этой переменной перечислены все (во всяком случае, так говорится в документа-
ции) MIME-типы данных, которые могут быть восприняты браузером. Как мы уже
замечали, современные браузеры частенько ленятся и передают строку */*, что озна-
чает, что они якобы понимают любой тип.

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

HTTP_USER_AGENT
Идентифицирует браузер пользователя. Если в данной переменной окружения при-
сутствует подстрока MSIE, то это — Internet Explorer, в противном случае, если в на-
личии лишь слово Mozilla, — Netscape.

HTTP_HOST
Доменное имя Web-сервера, на котором запустился сценарий. Эту переменную окру-
жения довольно удобно использовать, например, для генерации полного пути, кото-
рый требуется в заголовке Location, чтобы не привязываться к конкретному серверу
(вообще говоря, чем меньше сценарий задействует "зашитую" в него информацию об
имени сервера, на котором он запущен, тем лучше — в идеале ее не должно быть во-
все).

SERVER_PORT
Порт сервера (обычно 80), к которому обратился браузер пользователя. Также может
привлекаться для генерации параметра заголовка Location.

REMOTE_ADDR
Эта переменная окружения задает IP-адрес (или доменное имя) узла пользователя, на
котором был запущен браузер.

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

SCRIPT_NAME
Виртуальное имя выполняющегося сценария (то есть часть URL после имени сервера, но
до символа ?). Эту переменную окружения, опять же, очень удобно брать на вооружение
при формировании заголовка Location при переадресации на себя (self-redirect), а также
при проставлении значения атрибута action тэга <form> на странице, которую выдает
сценарий при запуске без параметров (для того чтобы не привязываться к конкретному
имени сценария).

REQUEST_METHOD
Метод, который применяет пользователь при передаче данных (мы рассматриваем
только GET и POST, хотя существуют и другие методы). Надо заметить, что грамотно
составленный сценарий должен сам определять на основе этой переменной, какой
метод задействует пользователь, и принимать данные из соответствующего источни-
ка, а не рассчитывать, что передача будет осуществляться, например, только методом
POST. Впрочем, все PHP-сценарии так и устроены.

QUERY_STRING
Параметры, которые в URL указаны после вопросительного знака. Напомню, что они
доступны как при методе GET, так и при методе POST (если в последнем случае они
были определены в атрибуте action тэга <form>).

CONTENT_LENGTH
Количество байтов данных, присланных пользователем. Эту переменную необходимо
анализировать, если вы занимаетесь приемом и обработкой POST-формы.

2.3.2. Формы

Все элементы формы по именам соответствующих им тэгов делятся на 3 категории:
- <input...>
- <textarea...>...</textarea>
- <select...><option...>...</option>...</select>
Каждый из этих тэгов, конечно, может иметь имя. Ранее уже упоминалось, что пары
имя=значение перед тем, как отправятся сценарию, будут разделены в строке пара-
метров символом &. Кроме того, следует учитывать, что для тех компонентов формы,
у тэгов которых не задан параметр name, соответствующая строка имя=значение
передана не будет. Это ограничение введено для того, чтобы можно было в форме
определять служебные элементы, которые не будут посылаться сценарию. Например,
в их число входят кнопки (подтверждения отправки или обычные, используемые при
программировании на JavaScript) и т. д. Так, создадим форму:

<form action=script.cgi>
... какие-то поля ...
<input type=submit value="Go!">
</form>

Несмотря на то,

что кнопка Go! формально является полем ввода, ее данные не будут
переданы сценарию, поскольку у нее отсутствует параметр name.
Чаще все же бывает удобно давать имена таким кнопкам. Например, для того, чтобы
определить, каким образом был запущен сценарий — путем нажатия на кнопку или
как-то еще (например, просто набором его URL в браузере). Создадим следующую
форму:

<form action=script.cgi>
<input type=submit name="submit" value="Go!">
</form>

После запуска такой формы и нажатия в ней кнопки Go! сценарию среди прочих па-
раметров будет передана строка submit=Go!. Вернувшись к примеру из предыдущей
главы, мы теперь легко сможем определить, был ли сценарий выполнен из формы
или же простым указанием его URL (для этого достаточно проанализировать команд-
ную строку сценария и определить, присутствует ли в ней атрибут submit).
В принципе, все тэги, за исключением <select>, с точки зрения сценария выглядят
одинаково — как один они генерируют строки вида имя=значение, где имя — то,
что задано в атрибуте name, а значение — либо текст, введенный пользователем,
либо содержимое атрибута value (например, так происходит у независимых и зави-
симых переключателей, которые мы вскоре рассмотрим).

Тэг <input> — различные поля ввода

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

Текстовое поле (text)

<input type=text
name=имя
[value=значение]
[size=размер]
[maxlen=число]
>

Создает поле ввода текста размером примерно в size знакомест и максимально до-
пустимой длиной maxlen символов (то есть пользователь сможет ввести в нем не
больше этого количества символов).
Если задано значение атрибута value, то в текстовом поле будет изначально отобра-
жена указанная строка.

Поле ввода пароля (password)

<input type=password
name=имя
[value=значение]
[size=размер]
[maxlen=число]
>

Полностью аналогичен тэгу <input type=text>, за исключением того, что симво-
лы, набираемые пользователем, не будут отображаться на экране. Это удобно, если
нужно запросить какой-то пароль. Кстати, если в качестве маски задается значение
параметра value, все будет в порядке, однако, посмотрев исходный HTML-текст
страницы в браузере, можно увидеть, что он (браузер) это значение не показывает
(непосредственно на странице). Сделано это, видимо, из соображений безопасности,
хотя, конечно же, злоумышленник легко преодолеет такую защиту, если вы попытае-
тесь скрыть с ее помощью что-то важное.

Скрытое текстовое поле (hidden)

<input type=hidden
name=имя
value=значение
>

Создает неотображаемое (скрытое) поле. Такой объект нужен исключительно для
того, чтобы передать сценарию какую-то служебную информацию, до которой поль-
зователю нет дела, — например, параметры настройки.
Пусть, например, у нас имеется многоцелевой CGI-сценарий, который умеет прини-
мать данные пользователя и отправлять их как почтовое сообщение. Поскольку мы
бы не хотели фиксировать E-mail получателя жестко, но в то же время и не стремим-
ся, чтобы пользователь мог его менять перед отправкой формы, оформим соответст-
вующий тэг в виде скрытого поля:

<form action=/cgi/sendmail.cgi method=post>
<input type=hidden name=email value="admin.microsoft.com.">
<h2>Пошлите сообщение администратору:</h2>
<input type=text name="text">
<input type=submit name=doSend value="Отослать">
</form>

Независимый переключатель (checkbox)

<input type=checkbox
name=имя
value=значение
[checked]
>

Этот тэг генерирует независимый переключатель (или флажок), который может быть
либо установлен, либо сброшен (квадратик с галочкой внутри или пустой соответст-
венно). Если пользователь установил этот элемент, прежде чем нажать кнопку дос-
тавки, сценарию поступит строка имя=значение, в противном случае не придет ни-
чего, будто нашего поля и не существует вовсе. Если задан атрибут checked, то
переключатель будет изначально установленным, иначе — изначально сброшенным.

Зависимый переключатель (radio)

<input type=radio
name=имя
value=значение
[checked]
>

Включение в форму этого тэга вызывает появление на ней зависимого переключате-
ля (или радиокнопки). Зависимый переключатель — это элемент управления, кото-
рый, подобно независимому переключателю, может находиться в одном из двух со-
стояний. С тем отличием, что если флажки не связаны друг с другом, то только одна
радиокнопка из группы может быть выбрана в текущий момент. Конечно, чаще всего
определяются несколько групп радиокнопок, независимых друг от друга. Наша кноп-
ка будет действовать сообща с другими, имеющими то же значение атрибута
name — иными словами, то же имя. Отсюда вытекает, что, в отличие от всех других
элементов формы, две радиокнопки довольно часто имеют одинаковые имена. Если
пользователь установит какую-то кнопку, сценарию будет передана строка
имя=значение, причем значение будет тем, которое указано в атрибуте value вы-
бранной кнопки (а все остальные переключатели проигнорируются, как будто неуста-
новленные флажки). Если указан параметр checked, кнопка будет изначально вы-
брана, в противном случае — нет.

Кнопка отправки формы (submit)

<input type=submit
[name=имя]
value=текст_кнопки
>

Создает кнопку подтверждения с именем name (если этот атрибут указан) и названи-
ем (текстом, выводимым поверх кнопки), присвоенным атрибуту value. Как уже го-
ворилось, если задан параметр name, после нажатия кнопки отправки сценарию вме-
сте с другими парами будет передана и пара имя=текст_кнопки (если нажата не эта
кнопка, а другая, будет передана строка другой, нажатой, кнопки). Это особенно
удобно, когда в форме должно быть несколько кнопок submit, определяющих раз-
личные действия (например, кнопки Сохранить и Удалить в сценарии работы с за-
писью какой-то базы данных) — в таком случае чрезвычайно легко установить, какая
же кнопка была нажата, и предпринять нужные действия.

Кнопка сброса формы (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).
- Virtual — наиболее удобный тип вывода. Справа от текстового поля выводится
полоса п

рокрутки, и текст, который набирает пользователь, внешне выглядит раз-
битым на строки в соответствии с шириной поля ввода, причем перенос осуществ-
ляется по словам. Однако символ новой строки вставляется в текст только при
нажатии <Enter>.
- Physical — зависит от реализации браузера, обычно очень похож на none.
- None — текст отображается в том виде, в котором заносится. Если он не умещает-
ся в текстовое поле, активизируются линейки прокрутки (в том числе, и горизон-
тальная).
После отправки формы текст, который ввел пользователь, будет, как обычно, пред-
ставлен парой имя=текст, аналогично тэгу однострочного поля ввода
<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, либо сама строка элемента списка).

Списки множественного выбора (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> — некоторое функциональ-
ное излишество, и с точки зрения сценария вполне может заменяться флажками и
радиокнопками.

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

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

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

В свое время я говорил, что все данные из формы при передаче их на сервер упако-
вываются в строку при помощи символов ?, & и =. Легко видеть, что при загрузке
файлов такой способ, хотя и приемлем, но будет существенно увеличивать размер
передаваемой информации. Действительно, ведь большинство файлов — бинарные, а
мы знаем, что при URL-кодировании данные таких файлов сильно "распухают" —
примерно в три раза (например, простой нулевой байт при URL-кодировании превра-
тится в %00). Это сильно замедлит передачу и увеличит нагрузку на канал. И вот, от-
части специально для решения указанной проблемы был изобретен другой формат
передачи данных, отличный от того, который мы до сих пор рассматривали.
В нем уже не используются пресловутые символы ? и &. Кроме того, похоже, в случае
применения такого формата передачи может быть задействован только метод POST,
но не метод GET. Нас это вполне устроит — ведь файлы обычно большие, и достав-
лять их через GET вряд ли разумно...
Если нужно указать браузеру, что в какой-то форме следует применять другой формат
передачи, следует в соответствующем тэге <form> задать атрибут
enctype=multipart/form-data. (Кстати говоря, если этот атрибут не указан, то
форма считается обычной, что эквивалентно enctype=application/x-www-formurlencoded
— именно так обозначается привычный нам формат передачи.) После
этого данные, поступившие от нашей формы, будут выглядеть как несколько блоков
информации (по одному на элемент формы). Каждый такой блок очень напоминает
HTTP-формат "заголовки-данные", используемый при традиционном формате пере-
дачи. Выглядит блок примерно так (\n, как всегда, обозначает символ перевода стро-
ки):

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

Например:

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

Данные, поступившие по нажатии кнопки 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).

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

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

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

вле-
ния — поле ввода текста с кнопкой Browse справа. Таким тэгом является разновид-
ность <input>:

<input type=file
name=имя_элемента
[value=имя_файла]
>

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

----------------127462537625367\n
Content-Disposition: form-data; name="имя_элемента";
A filename="каталог\имя_файла"\n \n
........
Бинарные данные этого файла любой длины.
Здесь могут быть совершенно любые
байты без всякого ограничения.
........
\n

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

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

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

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

Установка 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-кодирован. Таким об-
разом, я должен отметить неприятный факт, что придется писать еще и функцию
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-кодированы, в противном
случае возможны неожиданности.

Получение Cookies из браузера

Получить Cookies для сценария несколько проще: все они хранятся в переменной ок-
ружения HTTP_COOKIE в таком же формате, как и QUERY_STRING, только вместо &
используется ;. Например, если мы установили два Cookies: cookie1=value1 и
cookie2=value2, то в переменной окружения HTTP_COOKIE будет следующее:
cookie1=value1;cookie2=value2.
Сценарий должен разобрать эту строку, распаковать ее и затем работать по своему
усмотрению.

2.3.5. Авторизация

Часто бывает нужно, чтобы на какой-то URL могли попасть только определенные
пользователи. А именно, только те, у которых е

сть зарегистрированное имя (login) и
пароль (password). Механизм авторизации как раз и призван упростить проверку
данных таких пользователей.
Я не буду здесь рассматривать все возможности этого механизма по трем причинам.
Во-первых, существует довольно много типов авторизации, различающихся степенью
защищенности передаваемых данных. Во-вторых, при написании обычных CGI-
сценариев для того, чтобы включить механизм авторизации, необходимо провести
некоторые манипуляции с настройками (файлами конфигурации) сервера, что, скорее
всего, будет затруднительно (ведь обычно компания, которая предоставляет услуги по
обслуживанию виртуального хоста, не позволяет вмешиваться в настройки сервера).
И наконец, в-третьих, весь механизм авторизации значительно упрощается и унифи-
цируется при использовании PHP, и вам не придется ничего исправлять в этих злопо-
лучных настройках сервера. Так что давайте отложим практическое знакомство с ав-
торизацией и займемся ее теорией.
Расскажу вкратце о том, как все происходит на нижнем уровне при одном из самых
простых типов авторизации — basic-авторизации. Итак, предположим, что сценарий
посылает браузеру пользователя следующий заголовок:

WWW-Authenticate: Basic realm="имя_зоны"
HTTP/1.0 401 Unauthorized"

Обратите внимание на то, что последний заголовок несколько отличается по форме от
обычных заголовков. Так и должно быть. Строка имя_зоны в первом из них задает
некоторый идентификатор, который будет определять, к каким ресурсам будет раз-
решен доступ зарегистрированным пользователям. При программировании CGI-
сценариев этот параметр используется в основном исключительно для формирования
приветствия (подсказки) в диалоговом окне, появляющемся в браузере пользователя
(там отображается имя зоны), так что мы не будем вдаваться в детали относительно
него.
Затем, как обычно, посылается тело документа (сразу отмечу, что именно это тело
ответа будет выдано пользователю, если он нажмет в диалоговом окне (см. ниже)
кнопку Cancel, т. е. отменит вход). В этом случае происходит нечто удивительное: в
браузере пользователя появляется небольшое диалоговое окно, в котором предлагает-
ся вести login и password. После того как пользователь это сделает, управление пере-
дается обратно серверу, который среди обычных заголовков запроса (которые посы-
лает браузер) получает примерно такой:

Authorization: Basic TG9naW46UGFzcw==

Это — ни что иное, как закодированные данные, введенные пользователем. Теорети-
чески, далее этот заголовок должен каким-то образом передаться сценарию (для этого
как раз и необходимо добавление команд в файлы конфигурации сервера). Сценарий,
декодировав его, может решить: то ли повторить всю процедуру сначала (если имя
или пароль неправильные), или же начать работать с сообщением "OK, все в порядке,
вы — зарегистрированный пользователь".
Предположим, что сценарий подтвердил верность данных и "пропустил" пользовате-
ля. В этом случае происходит еще одна вещь: login и password пользователя запоми-
наются в скрытом Cookie, "живущем" в течение одной сессии работы с браузером.
Затем, что бы мы ни делали, заголовок

Authorization: Basic значение_Cookie

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

»На главную
© allPHP.pp.ru

ИТБ "Градиент" - установить сваи буронабивные с уширением быстро - 755-21-55 . эротика