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

Самоучитель PHP 4 - Котеров Д. В

..pdf
Скачиваний:
92
Добавлен:
24.05.2014
Размер:
4.38 Mб
Скачать

Глава 2

Интерфейс CGI

Термин CGI (Common Gateway Interface — Общий шлюзовой интерфейс) обозначает набор соглашений, которые должны соблюдаться Web-серверами при выполнении ими различных Web-приложений. Вскоре мы расшифруем его смысл гораздо более подробно. Фактически, до недавнего времени все Web-программирование представляло собой программирование CGI-приложений. В последнее время ситуация изменилась. И хотя CGI все еще остается негласным стандартом для Web-приложений, механизм работы CGI-программ несколько обновился.

В этой и следующей главах мы будем разбирать основы традиционного CGIпрограммирования, не касаясь напрямую PHP. В качестве языка для примеров выбран Си, поскольку его компиляторы можно найти практически в любой операционной системе, и по той причине, что он "наиболее красиво" показывает, почему… его не следует использовать в Web-программировании. Да-да, это не опечатка. Вскоре вы поймете, что я хотел сказать.

Что такое CGI?

Итак, мы набираем в нашем браузере

http://www.somehost.com:80/path/to/document.ext

Мы ожидаем, что сейчас получим HTML-документ (или документ другого формата — например, рисунок). Иными словами, мы рассчитываем, что на хосте в каталоге /path/to/ расположен файл document.ext, который нам сейчас и доставят (передаст его, кстати, Web-сервер, подключенный к порту 80 на сервере).

Однако на самом деле ситуация несколько иная. По двум причинам.

rПуть /path/to/, ровно как и файл document.ext на хосте может вообще не существовать. Ведь администратор сервера имеет возможность задать псевдоним (alias) для любого объекта на сервере. Кроме того, даже если и не назначено никакого псевдонима, все равно имеется возможность так написать программы для Web-сервера, что они будут "перехватывать" каждое обращение к таким путям и соответствующим образом реагировать на это (пример рассмотрен в главе 1).

rФайл document.ext может быть вовсе не текстовым документом, а программой, которая в ответ на наш запрос молниеносно запустится, не менее стремительно

Глава 2. Интерфейс CGI

29

выполнится и возвратит пользователю результаты своей работы, хотя бы в том же HTML-формате (или, разумеется, в любом другом, — например, это может быть изображение). Пользователь и не догадается, что на самом деле произошло. Для него все равно, загружает ли он документ или невольно запускает программу. Более того, он никак не сможет узнать, что же на самом деле случилось.

Последний пункт особенно впечатляющ. Если вы прониклись его идеей, значит, вы поняли в общих чертах, что такое CGI. Как раз CGI обеспечивает все то, что выглядит так прозрачно для пользователя. Традиционно программы, работающие в соответствии с соглашениями CGI, называют сценариями — скорее всего из-за того, что в большинстве случаев их пишут на языках-интерпретаторах, подобных Basic (напри-

мер, на Perl или PHP).

Задумаемся на мгновенье. Мы получили довольно мощный механизм, который позволяет нам, в частности, формировать документы "на лету". К примеру, пусть нам нужно, чтобы в каком-то документе проставлялись текущая дата и время. Разумеется, мы не можем заранее прописать их в документе — ведь в зависимости от того, когда он будет загружен пользователем, эта дата должна меняться. Зато мы можем написать сценарий, который вычислит дату, вставит ее в документ и затем передаст его пользователю, который даже ничего и не заметит!

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

Секреты URL

Помните, я выше описывал, как выглядит URL? Каюсь, приврал. На самом деле URL имеет более "длинный" вид:

http://www.somehost.com:80/path/to/document.ext?parameters

Как нетрудно заметить, может существовать еще строка parameters, следующая после вопросительного знака. В некоторой степени эта строка аналогична командной строке ОС. В ней может быть все, что угодно, она может быть любой длины (однако следует учитывать, что некоторые символы должны быть URL-закодированы, см. ниже). Вот как раз эта-то строка и передается CGI-сценарию.

30

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

На самом деле существуют некоторые ограничения на длину строки парамет- ров. Но нам приходится сталкиваться с ними слишком редко, чтобы имело смысл об этом говорить.

Вернемся к нашему предыдущему примеру. Теперь пользователь может указать свой часовой пояс сценарию, например, так:

http://www.somehost.com/script.cgi?time=+5

Сценарий с именем script.cgi, после того как он запустится и получит эту строку параметров, должен ее проанализировать (например, создать переменную time и присвоить ей значение +5, т. е. 5 часов вперед) и дальше работать как ему нужно. Обращаю ваше внимание на то, что принято параметры сценариев указывать именно в виде переменная=значение.

А если нужно передать несколько параметров (например, не только часовой пояс, но и имя пользователя)? Сделаем это следующим образом:

http://www.somehost.com/script.cgi?time=+5&name=Vasya

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

Так вот, такой способ посылки параметров сценарию (когда данные помещаются в командную строку URL) называется методом GET. Фактически, даже если не передается никаких параметров (например, при загрузке статической страницы), все равно применяется метод GET. Все? Нет, не все. Существует еще один распространенный способ (не менее распространенный) — метод POST, но давайте прежде рассмотрим, на каком языке "общаются" браузер и сервер.

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

Задумаемся на минуту, что же происходит, когда мы набираем в браузере строку somestring и нажимаем <Enter>. Браузер посылает серверу запрос somestring? Нет, конечно. Все немного сложнее. Он анализирует строку, выделяет из нее имя сервера и порт (а также имя протокола, но нам это сейчас не интересно), устанавливает соединение с Web-сервером по адресу сервер:порт и посылает ему что-то типа следующего:

GET somestring HTTP/1.0\n

Глава 2. Интерфейс CGI

31

...другая информация...

\n\n

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

Как видим, после GET-строки могут следовать и другие строки с информацией, разделенные символом перевода строки. Их обычно формирует браузер. Такие строки называются заголовками (headers), и их может быть сколько угодно. Протокол HTTP как раз и задает правила формирования и интерпретации этих заголовков.

Вот мы и начинаем знакомство с протоколом HTTP. Как видите, он представляет собой ни что иное, как просто набор заголовков, которыми обмениваются сервер и браузер, и еще пару соглашений насчет метода POST, которые мы вскоре рассмотрим.

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

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

Ниже приводятся некоторые заголовки запросов с их описаниями, а также имена переменных окружения, которые использует сервер для передачи их CGI-сценарию. Я указываю заголовки вместе с примерами в том контексте, в котором они могут быть применены, иными словами, вместе с наиболее распространенными их значениями. Так будет несколько нагляднее.

GET

rФормат: GET сценарий?параметры HTTP/1.0

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

32

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

Этот заголовок является обязательным (если только не применяется метод POST) и определяет адрес запрашиваемого документа на сервере. Также задаются параметры, которые пересылаются сценарию (если сценарию ничего не передается, или же это обычная статическая страница, то все символы после знака вопроса и сам знак опускаются). Вместо строки HTTP/1.0 может быть указан и другой протокол — например, HTTP/1.1. Именно его соглашения и будут учитываться сервером при обработке данных, поступивших от пользователя, и других заголовков.

Строка сценарий?параметры задается в том же самом формате, в котором она входит в URL. Неплохо было бы назвать эту строку как-нибудь более реалистично, чтобы учесть возможность присутствия в ней командных параметров. Такое название действительно существует и звучит как URI (Universal Resource Identifier — Универсальный идентификатор ресурса). Очень часто его смешивают с понятием URL (вплоть до того, что это происходит даже в официальной документации по стандартам HTTP). Давайте договоримся, что в будущем я всегда буду называть словом URL полный путь к некоторой Web-странице вместе с параметрами, и условимся, что под словом URI будет пониматься его часть, расположенная после имени (или IP-адреса) хоста и номера порта.

POST

rФормат: POST сценарий?параметры HTTP/1.0

rПеременная окружения: REQUEST_URI; в переменной QUERY_STRING сохраняется значение параметры, в переменной REQUEST_METHOD — слово POST.

Этот заголовок используется при передаче данных методом POST. Вскоре мы рассмотрим этот метод подробнее, а пока скажу лишь, что он отличается от метода GET тем, что данные можно передавать не только через командную строку, но и в конце всех заголовков.

Content-type

rФормат: Content-Type: application/x-www-form-urlencoded

rПеременная: CONTENT_TYPE

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

Хочу обратить ваше внимание на то, что сервер никак не интерпретирует рассматриваемый заголовок, а просто передает его сценарию через переменную окружения.

Глава 2. Интерфейс CGI

33

User-Agent

rФормат: User-Agent: Mozilla/4.5 [en] (Win95; I)

rПеременная окружения: HTTP_USER_AGENT

Уточняет версию браузера (в данном случае это Netscape Navigator).

Referer

rФормат: Referer: URL_адрес

rПеременная окружения: HTTP_REFERER

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

Вы, наверное, подумали, что слово referer пишется по-английски с двумя бук- вами "r". Да, вы правы. Однако те, кто придумывал стандарт HTTP, этого, ви- димо, не знали. Так что не позволяйте столь досадному факту ввести себя в заблуждение, когда будете в сценарии использовать переменную окружения

HTTP_REFERER.

Content-length

rФормат: Content-length: длина

rПеременная окружения: CONTENT_LENGTH

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

Cookie

rФормат: Cookie: значения_Cookies

rПеременная окружения: HTTP_COOKIE

Здесь хранятся все Cookies в URL-кодировке (о Cookies мы подробнее поговорим в следующей главе).

34

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

Accept

r Формат: Accept: text/html, text/plain, image/gif, image/jpeg r Переменная окружения: HTTP_ACCEPT

В этом заголовке браузер перечисляет, какие типы документов он "понимает". Перечисление идет через запятую. К сожалению, в последнее время браузеры стали несколько небрежны и часто присылают в этом заголовке значение */*, что обозначает любой тип.

Существует еще множество заголовков запроса (часть из них востребуются только протоколом HTTP 1.1), но мы не будем на них задерживаться.

Эмуляция браузера через telnet

Между прочим, при передаче запроса браузер "притворяется" пользователем, который запустил telnet-клиента (программу, которая, грубо говоря, умеет подключаться к заданному IP-адресу и порту, посылать по нему то, что набирается на клавиатуре, и отображать на экране поступающие "снаружи" данные) и вводит строки заголовков вручную — т. е., в текстовом виде. Например, вместо того чтобы набрать в браузере http://www.somehost.com/, попробуйте в командной строке ОС (Unix, Windows 95/98/NT/2000 или любой другой) выполнить следующие команды (вместо <Enter> нажимая соответствующую клавишу):

telnet www.somehost.com 80<Enter> GET /index.html HTTP/1.0<Enter> <Enter>

Вы увидите, как перед вами промелькнут строки HTML-документа index.html. Очень рекомендую проделать описанную процедуру, чтобы избавиться от духа мистицизма при упоминании о протоколе HTTP. Все это не так сложно, как иногда может показаться.

Если у вас указанная процедура не удалась, и сервер все время шлет сообще- ние "Bad Request", то проверьте регистр символов, в котором вы набираете команды. Все буквы должны быть заглавными, а название протокола

HTTP/1.0 идти без пробелов.

Посмотрим теперь, как работает сервер. А происходит все следующим образом: он считывает все заголовки запроса и дожидается маркера "\n\n" (или, что то же самое, "пустого" заголовка), а как только его получает, начинает разбираться — что же ему за информация пришла, и выполнять соответствующие действия.

С помощью заголовков реализуются такие механизмы, как контроль кодировок, Cookies, метод POST и т. д. Если же сервер не понимает какого-то заголовка, он его

Глава 2. Интерфейс CGI

35

либо пропускает, либо жалуется отправителю (в зависимости от воли администратора, который настраивал сервер).

Метод 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 без изменения.

Кодировки и форматы данных

Ранее упоминалось, что и в методе GET, и в методе POST данные доставляются в URL-кодированном виде. Что это значит?

Уж не знаю, откуда взялась эта дурная традиция (может, из стремления сохранить совместимость с древними программами, которыми вот уже лет 20 никто не пользуется), но почему-то все Интернет-сервисы — начиная от E-mail и заканчивая Web — как-то очень "не любят" байты со значениями, превышающими 127. Поэтому применяется изощренный способ перекодировки, который все символы в диапазонах 0 .. 32 и 128 .. 256 представляет в URL-кодированном виде. Например, если нам нужно закодировать символ с шестнадцатеричным кодом 9E,

36

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

это будет выглядеть так: %9E. Помимо этого, пробел представляется символом плюс (+). Так что будьте готовы к тому, что вашим сценариям будут передаваться данные именно в таком виде. В частности, все буквы кириллицы преобразуются в подобную абракадабру (соответственно, размер данных увеличивается примерно в 3 раза!). Поэтому программы должны всегда быть готовы перекодировать информацию туда-сюда-обратно.

Но это только пол-беды. Дело в том, что существует еще такая неприятная проблема, как кодировки символов кириллицы. И неприятно не столько то, что они существуют, сколько то, что они все не подчиняются никакому единому логическому правилу, в отличие он ASCII. Если при этом текст, который пришел, допустим, в кодировке KOI-8-R, просматривают в WIN-кодировке, получается редкостная путаница.

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

Существуют даже специальные программы, которые пытаются раскодировать текст, который по ошибке был преобразован несколько раз и потому приобрел нечитаемый вид. Одна из них почтовый декодер Лебедева, работающий в online-режиме. Само наличие таких программ красноречиво свидетельствует, как далеко все зашло в вопросе о статусе русских кодировок.

Что может быть глупее? А все по той причине, что нет строгого стандарта на кириллицу и что, якобы, где-то в мире существуют браузеры, которые не умеют перекодировать информацию. Скажите на милость, зачем они тогда вообще нужны, если не умеют делать даже такой простой вещи, как табличные преобразования? Или это сделано для тех, кто читает Web-страницы не через браузер, а по telnet'у? И почему же из-за жалкой горстки пользователей должна страдать остальная часть населения страны?

Ну ладно-ладно, я уже успокоился. Прошу прощения, что влез на стол и кричал. Давайте продолжим.

Глава 2. Интерфейс CGI

37

Что такое формы и для чего они нужны

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

Сначала рассмотрим метод GET. Даже программисту довольно утомительно набирать параметры в URL вручную. Всякие там ?, &, %... Представьте себе пользователя, которого принуждают это делать. К счастью, существуют удобные возможности языка HTML, которые, конечно, поддерживаются браузерами. И хотя я уже замечал, что в этой книге будет лишь немного рассказано о HTML, о формах мы поговорим очень подробно.

Итак, пусть у нас на сервере в корневом каталоге размещен файл сценария script.cgi (наверное, вы уже заметили, что расширение cgi принято присваивать CGI-сценариям, хотя, как уже упоминалось, никто не мешает вам использовать любое другое слово). Этот сценарий распознает 2 параметра: name и age. Где эти параметры задаются, мы пока не решили. При переходе по адресу http://www.somehost.com/script.cgi он должен отработать и вывести сле-

дующую HTML-страницу:

<html><body>

Привет, name! Я знаю, Вам age лет! </body></html>

Разумеется, при генерации страницы нужно name и age заменить на соответствующие значения, переданные в параметрах.

Передача параметров "вручную"

Давайте будем включать параметры прямо в URL, в строку параметров. Таким образом, если запустить в браузере

http://www.somehost.com/script.cgi?name=Vasya&age=20

мы получим страницу с нужным результатом:

<html><body>

Привет, Vasya! Я знаю, Вам 20 лет! </body></html>

Заметьте, что мы разделяем параметры символом &, а также используем знак равенства =. Это неспроста. Сейчас мы обсудим, почему.