1 (1119426), страница 3
Текст из файла (страница 3)
Например, псевдо-сервер (см. выше)целесообразно реализовать именно так. Однако никакой реальный серверне использует эту схему по причине её крайней неэффективности.Проблема в том, что обработка корректного клиентского GET-запросатребует копирования файла (указанного в URI запроса) в клиентскийсокет. Эта операция может занять много времени. Особенно нетерпимо то,что сервер в течение этого времени будет простаивать, ожидая завершенияобмена с файлом и сокетом. Ведь в это же время можно обрабатыватьзапросы других клиентов! Поэтому более эффективная (и более сложная вреализации) схема использует асинхронный ввод/вывод. Главную роль вэтой схеме играет системный вызов select(). Прочитать о том, как select()используется для асинхронного ввода/вывода можно в [4, гл.6]. Здесьотметим, что для достижения максимальной эффективности асинхроннымдолжен быть не только обмен с сокетами, но и обмен с файламиресурсами.
Для этого дескриптор открытого файла ресурса должен бытьпереведен в режим неблокирующего ввода с помощью системного вызоваioctl(). Заметим, что особенность реализации механизма сокетов в ОСUNIX такова, что открытые файловые дескрипторы не отличаются ототкрытых сокетов с точки зрения системного вызова select(), поэтомуselect() реагирует не только на «сокетные» события, но и на «файловые»события, что существенно упрощает реализацию асинхронноговвода/вывода.15ОБЩИЙ ИНТЕРФЕЙС ШЛЮЗА – ОСНОВНЫЕ ПОНЯТИЯОбщий интерфейс шлюза (или общий шлюзовой интерфейс – поанглийски – Common Gateway Interface - CGI) – это стандартизованныйпротокол, позволяющий подключить к веб-серверу внешние программыдля генерации содержимого запрашиваемых ресурсов. Такие программыназываются«шлюзами»,номыбудемиспользоватьболеераспространенный термин «CGI-программа».
Будем считать, что все CGIпрограммы модельного веб-сервера располагаются в директории cgi-bin издомашней директории сервера.В случае если ресурс, запрошенный клиентом есть не файл данных, аисполняемая программа, то веб-сервер запускает эту программу и выводэтой программы направляет серверу. CGI определяет, каким образоминформация о сервере и запросе передается в CGI-программу, и какимобразом CGI-программа передает информацию обратно.В отличие от обычных консольных приложений, получающихинформацию при запуске через аргументы командной строки, CGIпрограммы получают ее через набор переменных окружения.
В случаезапросов PUT и POST информация от клиента передается черезстандартный ввод (хотя и в этом случае переменные окружения такженесут информацию о запросе и т.д.). Обратно информация передаетсячерез стандартный вывод CGI-программы (как совокупность HTTPзаголовков, обязательным является заголовок Content-type).
Сервер долженперехватить стандартный вывод, сохранить информацию и передать её какответ клиенту. Можно было бы просто перенаправить стандартный выводв клиентский сокет без промежуточного сохранения вывода, но однако такне поступают по двум главным причинам: во-первых, заголовок Contentlength можно сгенерировать только после завершения вывода, и во-вторых,клиента можно обезопасить от сбоя в CGI-программе.Таким образом, CGI-программу можно написать на любом языкепрограммирования, который поддерживает понятия стандартного ввода ивывода и имеет доступ к переменным окружения.
А это – практическилюбой язык программирования, реализованный в ОС UNIX. Именно из-затакой простоты и гибкости CGI-интерфейс стал первым широкоприменяемым средством расширения функциональности веб-серверов исоздания динамических веб-сайтов. Однако достоинства этого интерфейсапрактически сразу превратились в его недостатки: поскольку на каждыйзапрос CGI-ресурса веб-сервер запускает особый процесс, то нагрузка нареальные сервера резко возрастает, т.к. накладные расходы ОС на запускпроцесса могут превысить расходы на собственно генерацию содержания.В настоящее время промышленные веб-сервера поддерживают CGIинтерфейс для совместимости, однако высоко нагруженные сайтысоздаются с помощью других технологий (серверные страницы,включаемые модули и т.п.).
Рассмотрение таких технологий находится запределами настоящего пособия.Рассмотрим набор переменных окружения, которые должен16поддерживать (и передавать в CGI-программу) модельный веб-сервер.Обратите внимание на то, что переменные делятся на 2 группы: свойствазапроса (извлекаются либо из свойств TCP/IP соединения, либо иззаголовков запроса) и свойства сервера.Имя переменнойCONTENT_TYPEПояснениеMIME-типсодержимогозапросаGATEWAY_INTERFACE Версия протоколаCGIREMOTE_ADDRIP-адрес клиентаREMOTE_PORTTCP-порт клиентаQUERY_STRINGАргументызапроса (GET)SERVER_ADDRIP-адрес сервераSERVER_NAMEДоменноеимясервераSERVER_PORTTCP-порт сервераSERVER_PROTOCOLВерсия протоколаHTTPSERVER_SOFTWAREСерверноепрограммноеобеспечениеSCRIPT_NAMEИмяCGIпрограммыиззапросаSCRIPT_FILENAMEПолный путь кфайлуCGIпрограммыDOCUMENT_ROOTПолный путь кдомашнейдиректориисервераHTTP_USER_AGENTИмя клиентскойпрограммыHTTP_REFERERПримерtext/plainCGI/1.1127.0.0.18845User=Igor&mail=igor@mail.ru127.0.0.5www.mysoft.ru9005HTTP/1.0Apache/1.3.12 (Unix)PHP/3.0.17/cgi-bin/cgitest1/usr/home/igor/wwwroot/cgibin/cgitest1/usr/home/igor/wwwrootMozilla/4.0(compatible; MSIE 5.0;Windows 98; DigExt)URI ресурса, с http://127.0.0.1/testpage.htmкоторого клиент lсделал текущийзапрос17Пример CGI-программыПриведем пример простой CGI-программы, написанной на языке Си.Си – не лучший язык программирования для CGI, однако в программе наСи хорошо видны особенности CGI-программ.
Наш пример выдает текстпростой HTML-страницы, сообщающей аргументы запроса и IP-адресклиентского браузера. Будем считать, что запрос имеет следующий вид:GET URI-нашей-программы? user=Igor&mail=igor@mail.ru HTTP/1.1… заголовки запроса …#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 и т.д.)18МЕТОДИЧЕСКИЕ УКАЗАНИЯ ПО ВЫПОЛНЕНИЮВТОРОГО ЭТАПА ЗАДАНИЯЦелью второго этапа задания является добавление поддержки CGI вранее разработанный модельный веб-сервер.Самая простая схема реализации CGI подразумевает, что серверзапускает отдельный процесс-потомок для обработки CGI-программы.Вывод программы перенаправляется во временный файл дляпоследующего включения этого файла в ответ на запрос. Временный файлиспользуется, т.к. программа может завершиться некорректно (и тогда еёрезультаты должны быть проигнорированы без передачи клиенту), атакже, чтобы узнать длину ответа и сформировать заголовок Contentlength.
После корректного завершения CGI-программы сервер генерируетответ на основе выдачи программы.При реализации CGI особенно проявляются недостатки первойсхемы реализации сервера (когда тот не приступает к обработкеследующего запроса, пока не выполнит до конца текущий). Действительно,в этом случае сервер простаивает в ожидании завершения CGI-программы,которая может работать произвольно долго. Заметим, что CGI-программы,будучи внешними по отношению к серверу, обязаны рассматриватьсясервером как потенциально ненадежные (т.е. аварийно завершающиеся,зацикливающиеся и т.д.
и т.п.). Поэтому единственно приемлемой схемойреализации является асинхронная. Однако здесь появляется своя проблема:как одновременно и асинхронно отслеживать как события ввода-вывода,так и момент завершения CGI-программ.Дело в том, что после запуска процесса сервер должен перейти вожидание событий двух видов: события ввода/вывода (от операций надсокетами и файловыми дескрипторами), а также события завершенияпроцесса-потомка(CGI-программы).Системныйвызовselect()отслеживает события только первого рода, а завершение процесса надообрабатывать особо. Один из вариантов, который можно использовать вэтом случае, выглядит так:При запуске процессов сервер заносит идентификатор потомка вспециальную таблицу активных CGI-процессов. Если эта таблица не пуста,то сервер выполняет вызов select() с ненулевым тайм-аутом (выбираетсякакое-либо относительно небольшое значение – например, полсекунды).