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

Котеров Д. В., Костарев А. Ф. - PHP 5. 2-е издание (В подлиннике) - 2008

.pdf
Скачиваний:
6287
Добавлен:
29.02.2016
Размер:
11.36 Mб
Скачать

34

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

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

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

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

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

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

GET

Формат:

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

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

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

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

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

35

того, что это происходит даже в официальной документации по стандартам HTTP). Давайте договоримся, что в будущем мы всегда будем называть словом URL полный путь к некоторой Web-странице вместе с параметрами, и условимся, что под словом URI будет пониматься его часть, расположенная после имени (или IP-адреса) хоста и номера порта.

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

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

Host

Формат:

Host: имя_хоста

Переменная окружения: HTTP_HOST.

В соответствии с протоколом HTTP 1.1 в Интернете на каждом узле может располагаться сразу несколько хостов. (Напоминаем, что узел имеет отдельный IP-адрес, и вполне типична ситуация, когда разные доменные имена соответствуют одному и тому же IP-адресу.) Поэтому должен существовать какой-то способ, с помощью которого браузер сможет сообщить серверу, к какому хосту он собирается обращаться. Заголовок Host как раз и предназначен для этой цели. В нем браузер указывает то же самое имя хоста, которое ввел пользователь в адресной строке.

36

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

В настоящий момент доступ к большинству сайтов можно получить только по протоколу HTTP 1.1 (но не 1.0), а значит, указание заголовка Host является практически обязательным.

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

UseCanonicalName on).

Существует одно важное отличие между переменными HTTP_HOST и SERVER_NAME. Дело в том, что SERVER_NAME никогда не включает номер порта, к которому подключился браузер (обычно 80), в то время как HTTP_HOST, наоборот, содержит значение вида хост:порт, когда порт отличен от 80.

Какую же переменную использовать в скриптах? Вероятнее всего, HTTP_HOST, но в случае, если она не установлена (например, используется HTTP 1.0, где данный заголовок не поддерживается), подставлять вместо нее SERVER_NAME.

User-Agent

Формат:

User-Agent: Mozilla/4.5 [en] (Win95; I)

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

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

Referer

Формат:

Referer: URL_адрес

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

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

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

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

37

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), но мы не будем на них задерживаться.

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

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

telnet example.com 80<Enter>

GET /index.html HTTP/1.1<Enter>

Host: example.com<Enter>

<Enter>

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

38

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

при упоминании о протоколе HTTP. Все это не так сложно, как иногда может показаться.

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

должны быть заглавными, а название протокола HTTP/1.1 — идти без пробелов.

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

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

Метод POST

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

POST /script.cgi HTTP/1.1\n

Host: example.com

Content-length: 5\n

\n

Test!

Сервер начнет обработку запроса, не дожидаясь передачи данных после маркера конца заголовков. Иными словами, сценарий запустится сразу же после отправки \n\n, а уж ждать или не ждать, пока придет строка Test! длиной 5 байтов — его дело.

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

Зачем нужен метод POST? В основном для того, чтобы передавать большие объемы данных. Например, при загрузке файлов через Web (см. гл. 3) или при обработке больших форм. Кроме того, метод POST часто используют для эстетических целей: дело в том, что при применении GET, как вы, наверное, уже заметили, URL сценария становится довольно длинным и неизящным, а POST-запрос оставляет URL без изменения.

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

39

URL-кодирование

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

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

Проблема русских кодировок

URL-кодирование — это только полбеды. Дело в том, что существует еще такая неприятная проблема, как кодировки символов кириллицы. И неприятно не столько то, что они существуют, сколько то, что все они не подчиняются никакому единому логическому правилу, в отличие от ASCII.

Что такое кодировка символов?

Как известно, любой символ текста, который мы видим на экране, внутри компьютера хранится в виде некоторого числа — так называемого кода символа. Программы могут работать только с такими кодами: о том, как в действительности выглядит буква, они не догадываются.

Долгое время для представления кода символа использовалась ячейка памяти размером 1 байт. Это позволяло кодировать до 256 символов текста. Например, русской букве "с" соответствует код 241, а английской "с" — код 99. Выглядят оба символа одинаково, а кодируются по-разному.

Соответствие внешнего вида символа его коду называется кодировкой. Выше мы говорили про код русской буквы "с", но следовало бы при этом добавить, что это ее код в кодировке Windows-1251 (сейчас используется по умолчанию в Windows). Соответственно, код этой же буквы в кодировке KOI8-R будет другим, а именно — 211. Если текст, который пришел, допустим, в кодировке KOI8-R, просматривают

вкодировке Windows-1251, получается редкостная путаница.

Впоследние несколько лет все более популярной становится единая "многобайтовая" кодировка Unicode (или ее "разновидность" UTF-8), в которой для кодирования символов используются два и более байтов. Это позволяет хранить в документе тексты сразу на нескольких языках — ведь возможностей однобайтовой кодировки едва-едва хватает да-

40

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

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

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

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

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

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

Протокол HTTP и язык HTML поддерживают два разных способа указания кодировки документа.

Указание сценарием (или сервером) заголовка вида

Content-type: text/html; charset=windows-1251

(например, для кодировки Windows-1251 и MIME-типа text/html).

Указание тега

<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">

прямо в HTML-документе (обычно в секции <head>).

По сути, оба метода одинаковы: браузеру тем или иным способом передается заголовок Content-type с указанной кодировкой.

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

41

"Русский Apache" и кодировка

Какой из приведенных способов идеологически более правильный? Считается, что второй: ведь кодировка документа — это неотъемлемая часть самого документа, а потому она должна быть интегрирована в него.

Но давайте рассмотрим ситуацию, когда браузер пользователя "не понимает" той кодировки, которая указана в документе. Например, он поддерживает KOI8-R, но не поддерживает Windows-1251, или наоборот.

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

При каждом запросе браузер передает серверу заголовок Accept-charset, в котором указывает предпочтительную для него кодировку. Если у хостинг-провайдера установлен так называемый "русский Apache" (Apache с поддержкой популярного некогда модуля mod_charset, сайт которого — http://apache.lexa.ru), существует вероятность, что сервер (например, Apache) динамически перекодирует документ в ту кодировку, которую запросил клиент, и вернет ему страницу в правильной кодировке. Конечно, Apache также подправит имя кодировки в заголовке Content-Type, который посылается браузеру.

Но ведь сервер "ничего не знает" ни о HTML, ни о теге <meta>. А значит, он не сможет исправить кодировку в этом теге, если автор страницы укажет ее там! Приходим к выводу, что указание кодировки при помощи тега <meta> может быть несовместимо с "русским Apache". Мы должны отказаться либо от одного, либо от другого. В противном случае возможны весьма неприятные ситуации, и один из примеров мы здесь рассмотрим.

Пример сбойной конфигурации

Пусть на сервере установлен "русский Apache", кодировка документов — Windows1251. Во всех HTML-файлах присутствует тег <meta> с указанием той же самой кодировки.

Казалось бы, все верно: в обоих местах указана одинаковая кодировка, и проблем быть не должно. К сожалению, это не так!

Представим себе браузер, умеющий работать со всеми кодировками, но по умолчанию он желает видеть KOI8-R. На сервер приходит запрос:

GET / HTTP/1.1

Host: example.com

Accept-charset: KOI8-R

Запускается модуль "русского Apache", который видит, что клиент требует выдать ему документ в формате KOI8-R. Он также знает, что файлы на сервере хранятся в кодировке Windows-1251. Не долго думая, сервер перекодирует страницу, меняет заголовок Content-Type и отправляет ответ назад в браузер.

Но ведь он не может изменить тег <meta>!

42

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

Что получается в итоге? Браузер получает документ в кодировке KOI8-R. В заголовке Content-Type у него также прописано KOI8-R. Однако во время анализа страницы браузер натыкается на тег <meta> с указанной кодировкой Windows-1251 (он, как вы помните, не изменялся сервером), и, т. к. указанные при помощи <meta> заголовки, имеют приоритет над "обычными" заголовками, браузер переключается назад в Windows-1251.

В результате пользователь видит на экране документ в KOI8-R, отображенный при помощи таблицы кодировки Windows-1251. Иными словами, он видит полную абракадабру.

Рассмотренный пример не "взят с потолка", это вполне реальный случай, встречающийся весьма часто. Существует много браузеров под Unix со включенной по умолчанию кодировкой KOI8-R.

Отключение автоматической перекодировки

Надеемся, приведенный выше пример помог понять, почему одновременное указание кодировки при помощи тега <meta> и заголовка недопустимо — даже в случае, если кодировки совпадают и соответствуют документу. Иными словами, "русский Apache" несовместим с указанием кодировки в теге <meta>.

Но как же быть с "идеологическими соображениями" о пользе указания кодировки в самом документе, которые мы приводили выше? Есть только один выход: если вы хотите использовать тег <meta> на своих страницах, удостоверьтесь, что "русский Apache" отключен.

В последнее время все меньше и меньше хостеров устанавливают у себя "русский Apache" именно по описанной выше причине. Тем не менее не стоит полагаться на случай: лучше отключить mod_charset самостоятельно. Делается это при помощи следующей директивы Apache, которую необходимо поместить в файл .htaccess в каталоге документов сервера:

<IfModule mod_charset.c>

CharsetDisable on

</IfModule>

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

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

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

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

43

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

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

<html><body>

Привет, name! Я знаю, Вы родились born!

</body></html>

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

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

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

http://example.com/script.cgi?name=Thomas&born=1962-03-11

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

<html><body>

Привет, Thomas! Я знаю, Вы родились 1962-03-11!

</body></html>

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

Использование формы

Что теперь нам следует сделать, чтобы пользователь мог в удобной форме ввести свое имя и возраст? Очевидно, придется создать что-то вроде диалогового окна Windows, только в браузере. Итак, нам понадобится обычный HTML-документ (например, с именем getform.htm и расположенный в корневом каталоге) с элементами этого диалога — полями ввода и кнопкой, при нажатии которой запустится наш сценарий. Текст этого документа приведен в листинге 2.1.

Листинг 2.1. Файл getform.htm

<!-- Документ с формой. --> <html><body>

<form action=script.cgi method=GET> Введите имя:

<input type=text name="name" value="Неизвестный"><br>