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

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

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

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

41

решение этой проблемы, которое рекомендуется применять всюду, где это только возможно: пусть сценарий в первую очередь проверяет, запущен ли он с параметрами или без них. Если параметров нет, то сценарий выдает пользователю HTML-документ с формой, в противном случае — результаты работы. Это удобно еще и потому, что, возможно, вы захотите, чтобы пользователь обязательно ввел свое имя. То есть, если он забудет это сделать, ему будет выдана все та же форма с сообщением напротив поля ввода для имени: "Извините, но Вы забыли ввести свое имя. Попробуйте еще, вдруг на этот раз получится?". А в следующей главе мы попутно рассмотрим, как проще всего определить, был запущен сценарий по нажатии кнопки или же просто набором его URL в браузере.

Приведенная схема минимизации количества документов стандартна и весьма универсальна (ее применют 99% сценариев, которые можно найти в Интерне- те). Она еще и удобна для пользователя, потому что не создает "мертвых" ссылок (любой URL сценария, который он наберет, пусть даже и без парамет- ров, будет корректным). Однако программирование этой схемы на Си (и на не- которых других языках) вызывает определенные проблемы. Язык PHP таких проблем лишен.

Глава 3

CGI изнутри

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

И это только небольшая часть вопросов, которые пока остаются открытыми. В этой главе я постараюсь вкратце описать, как же должны на самом деле быть устроены внутри CGI-сценарии. На мой взгляд, каждый программист обязан хотя бы в общих чертах знать, как работает то, что он использует — будь то операционная система (ОС) или удобный язык-интерпретатор для написания CGI-сценариев (каким является PHP). А значит, речь пойдет о программировании на Си. Я выбрал Си, т. к. это одно из самых лучших и лаконичных средств; кроме того, именно на Си чаще всего пишут те сценарии, которым требуется максимально критичное быстродействие (базы данных, поисковые системы, системы почтовой рассылки с сотнями тысяч пользователей и др.). В пользу этого языка говорит также и то, что его компиляторы можно встретить практически в любой сколько-нибудь серьезной ОС.

Тем не менее, вы не найдете в этой главе ни одной серьезной законченной программы на Си (за исключением разве что самой простой, типа "Hello, world!"). Несмотря на это, я попытаюсь описать практически все, что может понадобиться при программировании сценариев на Си (кроме работы с сокетами, — это тема для отдельной книги, да и, пожалуй, лишь косвенно примыкает к Web-программированию). По возможности я не буду привязываться к специфике конкретной ОС, ведь для CGI существует стандарт, независимый от операционной системы, на которой будет выполняться сценарий. Вооружившись материалом этой главы, можно написать самые разнообразные сценарии — от простых до самых сложных (правда, для последних потребуется также недюжинная сноровка).

И все-таки, моя цель — набросать общими мазками, как неудобно (повторюсь — именно неудобно!) программировать сценарии на языках, обычных для прикладного программиста (в том числе на Си и Си++). Как только вы проникнетесь этой идеей, мы плавно и не торопясь двинемся в мир PHP, где предусмотрены практически все удобства, так необходимые серьезному языку программирования сценариев.

Если вы не знакомы с языком Си, не отчаивайтесь. Все примеры хорошо комментированы, а сложные участки не нуждаются в непременном понимании "с первого про-

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

43

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

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

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

Вначале рассмотрим более простой вопрос: как программа посылает свой ответ (то есть документ) пользователю.

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

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

Заголовки ответа

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

44

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

его значение, разделенные двоеточием. Наличие пустого заголовка в конце также можно интерпретировать как два стоящих подряд обозначения \n\n. Затем, как обычно, могут следовать данные ответа, которые и являются документом, который будет отображен браузером.

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

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

HTTP/1.1 OK

или так:

HTTP/1.1 404 File Not Found

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

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

Вот другие наиболее распространенные заголовки ответа.

Content-type

r Формат: Content-type: mime_тип; charset=koi8-r

Задает тип документа и его кодировку. Параметр charset задает кодировку документа (в нашем примере это KOI8-R). Поле mime_тип определяет тип информации, которую содержит документ:

rtext/html — HTML-документ;

rtext/plain — простой текстовый файл;

rimage/gif — GIF-изображение;

rimage/jpeg — JPG-изображение;

rеще несколько десятков других типов.

Pragma

Формат: Pragma: no-cache

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

45

Запрещает кэширование документа браузером, так что при повторном визите на страницу браузер гарантированно загрузит ее снова, а не извлечет из своего кэша. Это может быть полезно, если страница содержит, например, динамический счетчик посещений.

Заголовок Pragma используется также и для других целей (и соответственно, после двоеточия находятся другие значения строки), но мы не будем их здесь рассматривать.

Location

Формат: Location: http://www.otherhost.com/somepage.html

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

Рекомендуется всегда указывать в заголовке Location абсолютный путь вме- сте с именем хоста, а не относительный. Дело в том, что, как показывает прак- тика, не все браузеры правильно реагируют на относительные пути и вытво- ряют все, что им заблагорассудится.

В браузере Netscape имеется ошибка, проявляющаяся, когда сценарий выво- дит заголовок Location с указанием перейти на собственный URL (то есть, сам на себя, для этого даже придуман специальный термин self-redirect). Такое решение не так бесполезно, как кажется, и используется, например, в гостевых книгах. В этом случае Netscape прекрасно принимает ответ сценария, но затем почему-то сообщает о том, что "документ не содержит данных". Как решить указанную проблему, см. в части V книги.

Set-cookie

Формат: Set-cookie: параметры_cookie

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

Date

Формат: Date: Sat, 08 Jan 2000 11:56:26 GMT

Указывает браузеру дату отправки документа.

46

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

Server

Формат: Server: Apache/1.3.9 (Unix) PHP/3.0.12

Устанавливается сервером и указывает браузеру тип сервера и другую информацию о серверном программном обеспечении.

Пример CGI-сценария

Настало время привести небольшой сценарий на Си, который иллюстрирует некоторые возможности, которые были описаны выше (листинг 3.1).

Листинг 3.1. Простейший сценарий script.c

#include <time.h>

// Нужна для инициализации функции rand()

#include <stdio.h>

// Включаем поддержку функций ввода/вывода

#include <stdlib.h>

// А это — для поддержки функции rand()

// Главная функция. Именно она и запускается при старте сценария. void main(void) {

//инициализируем генератор случайных чисел int Num; time_t t; srand(time(&t));

//в Num записывается случайное число от 0 до 9 Num = rand()%10;

//далее выводим заголовки ответа. Тип — html-документ printf("Content-type: text/html\n");

//запрет кэширования

printf("Pragma: no-cache\n");

//пустой заголовок printf("\n");

//выводим текст документа — его мы увидим в браузере printf("<html><body>"); printf("<h1>Здравствуйте!</h1>");

printf("Случайное число в диапазоне 0-9: %d",Num); printf("</body></html>");

}

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

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

47

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

http://www.myhost.com/cgi-bin/script.cgi

Мы получим нашу HTML-страницу. Заметьте, что при нажатии Reload (а также при повторном посещении страницы) браузер перезагрузит страницу целиком, а не возьмет ее копию из своего кэша (это можно видеть по постоянно изменяющемуся случайному числу или по лампочкам модема). Мы добились такого результата благодаря заголовку

Pragma: no-cache

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

Content-type: image/gif

Затем копируем один-в-один нужный нам GIF-файл в стандартный поток вывода (лучше всего — функцией fwrite, т. к. иначе могут возникнуть проблемы с "бинарностью" GIF-рисунка). Теперь можно использовать этот сценарий даже в таком контексте:

... какой-то текст страницы ...

<img src=http://www.myhost.com/cgi-bin/script.cgi>

... продолжение страницы ...

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

Еще раз обращаю ваше внимание на такой момент: CGI-сценарии могут использоваться не только для вывода HTML-информации, но и для любого другого ее типа — начиная с графики и заканчивая звуковыми MIDI-файлами. Тип документа задается в единственном месте — заголовке Content-type. Не забывайте добавлять этот заголовок, в противном случае пользователю будет отображена стандартная страница сервера с сообщением о 500-й ошибке (для сервера Apache), из которой он вряд ли что поймет.

48

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

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

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

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

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

HTTP_ACCEPT

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

HTTP_REFERER

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

HTTP_USER_AGENT

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

HTTP_HOST

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

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

49

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

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-формы.

50

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

Передача параметров методом GET

Тут все просто. Все параметры передаются единой строкой (а именно, точно такой же, какая была задана в URL после ?) в переменной QUERY_STRING. Единственная проблема — то, что все данные поступят URL-кодированными. Так что нам понадобится функция декодирования. Но это отдельная тема, пока мы не будем ее касаться.

Для того чтобы узнать значения полученных переменных в Си, нужно воспользоваться функцией getenv(). Вот пример сценария на Си, который это обеспечивает.

Листинг 3.2. Работа с переменными окружения

#include <stdio.h> // Включаем функции ввода/вывода

#include <stdlib.h> // Включаем функцию getenv()

void main(void) {

//получаем значение переменной окружения REMOTE_ADDR char *RemoteAddr = getenv("REMOTE_ADDR");

//... и еще QUERY_STRING

char *QueryString = getenv("QUERY_STRING"); // печатаем заголовок

printf("Content-type: text/html\n\n"); // печатаем документ

printf("<html><body>");

printf("<h1>Здравствуйте. Мы знаем о вас все!</h1>"); printf("Ваш IP-адрес: %s<br>",RemoteAddr);

printf("Вот параметры, которые Вы указали: %s",QueryString); printf("</body></html>");

}

Откомпилируем сценарий и поместим его в "CGI-каталог". Теперь в адресной строке введем:

http://www.myhost.com/cgi-bin/script.cgi?a=1&b=2

Мы получим примерно такой документ:

Здравствуйте. Мы знаем о Вас все!

Ваш IP-адрес: 192.232.01.23

Вот параметры, которые Вы указали: a=1&b=2