remix (1119427), страница 4
Текст из файла (страница 4)
Будем считать, что все CGI-программы модельного веб-сервера располагаются в директории cgi-bin из домашней директории сервера.В случае если ресурс, запрошенный клиентом есть не файл данных,а исполняемая программа, то веб-сервер запускает эту программу и выводэтой программы направляет серверу. CGI определяет, каким образом информация о сервере и запросе передается в CGI-программу, и каким образомCGI-программа передает информацию обратно.В отличие от обычных консольных приложений, получающих информацию при запуске через аргументы командной строки, CGI-программы получают ее через набор переменных окружения. В случае запросов PUT иPOST информация от клиента передается через стандартный ввод (хотя и вэтом случае переменные окружения также несут информацию о запросе ит.д.).
Обратно информация передается через стандартный выводCGI-программы (как совокупность HTTP-заголовков, обязательным являетсязаголовок Content-type). Сервер должен перехватить стандартный вывод,сохранить информацию и передать её как ответ клиенту. Можно было быпросто перенаправить стандартный вывод в клиентский сокет без промежуточного сохранения вывода, но однако так не поступают по двум главнымпричинам: во-первых, заголовок Content-length можно сгенерировать толькопосле завершения вывода, и во-вторых, клиента можно обезопасить от сбоя вCGI-программе.Таким образом, CGI-программу можно написать на любом языке программирования, который поддерживает понятия стандартного ввода и выводаи имеет доступ к переменным окружения. А это — практически любой языкпрограммирования, реализованный в ОС UNIX.
Именно из-за такой простотыи гибкости CGI-интерфейс стал первым широко применяемым средствомрасширения функциональности веб-серверов и создания динамическихвеб-сайтов. Однако достоинства этого интерфейса практически сразу превратились в его недостатки: поскольку на каждый запрос CGI-ресурсавеб-сервер запускает особый процесс, то нагрузка на реальные сервера резковозрастает, т.к. накладные расходы ОС на запуск процесса могут превыситьрасходы на собственно генерацию содержания.
В настоящее время промышленные веб-сервера поддерживают CGI-интерфейс для совместимости,однако высоко нагруженные сайты создаются с помощью других технологий(серверные страницы, включаемые модули и т.п.). Рассмотрение таких технологий находится за пределами настоящего пособия.Рассмотрим набор переменных окружения, которые должен поддерживать (и передавать в CGI-программу) модельный веб-сервер. Обратитевнимание на то, что переменные делятся на 2 группы: свойства запроса (из19Методические указания по выполнению первого этапа заданиявлекаются либо из свойств TCP/IP соединения, либо из заголовков запроса) исвойства сервера.Таблица 2.
Переменные окружения модельного веб-сервераИмя переменнойПояснениеПримерCONTENT_TYPEMIME-тип содержимогозапросаtext/plainGATEWAY_INTERFACEВерсия протокола CGICGI/1.1REMOTE_ADDRIP-адрес клиента127.0.0.1REMOTE_PORTTCP-порт клиента8845QUERY_STRINGАргументы запроса (GET)User=Igor&mail=igor@mail.ruSERVER_ADDRIP-адрес сервера127.0.0.5SERVER_NAMEДоменное имя сервераwww.mysoft.ruSERVER_PORTTCP-порт сервера9005SERVER_PROTOCOLВерсия протокола HTTPHTTP/1.0SERVER_SOFTWAREСерверное программноеобеспечениеApache/1.3.12 (Unix)PHP/3.0.17SCRIPT_NAMEИмя CGI-программы иззапроса/cgi-bin/cgitest1SCRIPT_FILENAMEПолный путь к файлуCGI-программы/usr/home/igor/wwwroot/cgi-bin/cgitest1DOCUMENT_ROOTПолный путь к домашнейдиректории сервера/usr/home/igor/wwwrootHTTP_USER_AGENTИмя клиентской программыMozilla/4.0(compatible; MSIE 5.0;Windows 98; DigExt)HTTP_REFERER URIURI ресурса, с которогоклиент сделал текущийзапросhttp://127.0.0.1/testpage.html4.2.3 Пример CGI-программыПриведем пример простой CGI-программы, написанной на языке Си.
Си —не лучший язык программирования для CGI, однако в программе на Си хорошо видны особенности CGI-программ. Наш пример выдает текст простойHTML-страницы, сообщающей аргументы запроса и IP-адрес клиентскогобраузера. Будем считать, что запрос имеет следующий вид:GET URI-нашей-программы?user=Igor&mail=igor@mail.ru HTTP/1.120Методические указания по выполнению первого этапа задания...заголовки запроса...Пример:#include <stdio.h>#include <stdlib.h>int main (){/* выдаем обязательный заголовок Content-typeи пустую строку перед телом сообщения */printf("Content-type: text/html\n\n");// выдаем текст собственно страницыprintf ( "<html><body>" );printf ( "Приветствую! Вы ввели аргументы:’%s’ садреса ’ %s’", getenv ( QUERY_STRING ), getenv( REMOTE_ADDR ) );printf ( "</body></html>" );return 0;}Как видим, структура CGI-программы очень проста, однако если мыпопытаемся написать более содержательный пример (например, с разборомQUERY_STRING и т.п.), то недостатки языка Си как языка программированиядля CGI проявятся более ярко.
Именно поэтому для программирования CGIобычно используются более подходящие языки (Perl, Python, PHP и т.д.)21Методические указания по выполнению второго этапа задания5. Методические указанияпо выполнению второгоэтапа заданияЦелью второго этапа задания является добавление поддержки CGI в ранееразработанный модельный веб-сервер.Самая простая схема реализации CGI подразумевает, что сервер запускает отдельный процесс-потомок для обработки CGI-программы.
Выводпрограммы перенаправляется во временный файл для последующего включения этого файла в ответ на запрос. Временный файл используется, т.к.программа может завершиться некорректно (и тогда её результаты должныбыть проигнорированы без передачи клиенту), а также, чтобы узнать длинуответа и сформировать заголовок Content-length. После корректного завершения CGI-программы сервер генерирует ответ на основе выдачи программы.При реализации CGI особенно проявляются недостатки первой схемыреализации сервера (когда тот не приступает к обработке следующего запроса, пока не выполнит до конца текущий).
Действительно, в этом случаесервер простаивает в ожидании завершения CGI-программы, которая можетработать произвольно долго. Заметим, что CGI-программы, будучи внешними по отношению к серверу, обязаны рассматриваться сервером как потенциально ненадежные (т.е. аварийно завершающиеся, зацикливающиеся ит.д. и т.п.). Поэтому единственно приемлемой схемой реализации являетсяасинхронная.
Однако здесь появляется своя проблема: как одновременно иасинхронно отслеживать как события ввода-вывода, так и момент завершения CGI-программ.Дело в том, что после запуска процесса сервер должен перейти в ожидание событий двух видов: события ввода/вывода (от операций над сокетамии файловыми дескрипторами), а также события завершения процесса-потомка (CGI-программы). Системный вызов select() отслеживает событиятолько первого рода, а завершение процесса надо обрабатывать особо. Одиниз вариантов, который можно использовать в этом случае, выглядит так:22Методические указания по выполнению второго этапа задания— При запуске процессов сервер заносит идентификатор потомка вспециальную таблицу активных CGI-процессов.
Если эта таблица непуста, то сервер выполняет вызов select() с ненулевым тайм-аутом(выбирается какое-либо относительно небольшое значение — например, полсекунды). В результате вызов select() завершается либопо приходу события от операций ввода/вывода, либо по истечениютайм-аута. После обработки событий ввода/вывода сервер не переходит сразу к ожиданию, а перед этим запрашивает ОС о завершениипроцессов-потомков. Для этого следует использовать системныйвызов// вызов неблокирующий — WNOHANGwaitpid(-1, &status, WNOHANG );Возможно, понадобится несколько вызовов waitpid() в случае, еслисразу несколько CGI-программ успели завершиться за время выполнениявызова select().
После того, как все завершившиеся к этому моменту процессыобработаны, сервер снова переходит в режим ожидания событий ввода/вывода (т.е. вызывает select()).Отдельного разговора заслуживает вопрос о последовательности обработки событий: то ли выполнять вначале все операции accept(), затемввода/вывода, затем обработку завершившихся потомков, то ли наоборот(вначале потомки, потом ввод-вывод, потом accept()), то ли использоватьочередность запросов (если запрос обрабатывается дольше всех, то его события по его обработке выполнять в первую очередь) и т.д.
В общем случае,ответ на этот вопрос и выбор стратегии «балансировки нагрузки навеб-сервер» весьма нетривиален, и его обсуждение выходит за рамки данногозадания.Итак, последовательность системных вызовов для одного шага циклаасинхронной обработки может быть такой:// формирование масок для вызова select()select()// вышли из select() по тайм-ауту, либо по наступлениюсобытий// . . .// обработка входящих соединенийaccept()// . . .// обработка операций ввода-выводаread()write()// . .
.// обработка завершившихся CGI-программwaitpid()// генерация ответа клиенту// чистка ресурсов (удаление временных файлов и т.д.)Последовательность системных вызовов для обработки CGI-запроса:pid = fork ();23Методические указания по выполнению второго этапа заданияif ( 0 < pid ){/* родитель-сервер — продолжаем асинхронную обработкусобытий */}else if ( pid < 0 ){// катастрофическая ошибка//...}else{/* потомок — обработка CGI-программы:— формирование массива переменных окружения env— перенаправление стандартного вывода во временный файл— перенаправление стандартного ввода (для метода POST)— собственно запуск программы */execvpe ( script_filename, argv, env );// обработка ошибок запуска}24Методические указания по выполнению третьего этапа задания6.
Методические указанияпо выполнению третьегоэтапа заданияНа третьем этапе требуется разработать интерпретатор модельного языка ивстроить его в веб-сервер, реализованный на предыдущих этапах выполнениязадания.Как уже отмечалось выше, для написания CGI-программ пригоденпрактически любой язык программирования, поддерживающий стандартныйввод/вывод и считывание значений переменных окружения. Однако специфика CGI-программ, а именно — ориентация на генерацию текста (точнее,текста на языке HTML), приводит к тому, что использование традиционныхязыков системного программирования типа Си/Си++ оказывается неудобным: соответствующие CGI-программы получаются громоздкими, труднымидля понимания и сопровождения.