Меню Новости Программы Статьи Форум Контакты

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Статьи
 

СтатьиПрограммированиеDelphi

 

Создание своего web-сервера

Автор: Pascal

Привет всем! Я Pascal. Это моя первая статья, поэтому прошу не судить строго.

В данной статье я расскажу, как делается Web-сервер при помощи языка программирования Delphi, без использования таких компонентов как “idHTTP”, “idHTTPServer”, честно говоря, даже не знаю, что они делают. По моему скромному мнению лучше изучить, как работает определённый протокол, и реализовать этот протокол в своей программе, а не использовать готовые компоненты, так как это не дает полного понимания, того, что ты делаешь.

Перед прочтением этой статьи можете прочитать спецификацию протокола HTTP, а можете и не читать :-).

Итак, протокол HTTP, как и многие другие(FTP, SMTP, POP и т. д.) базируется на TCP/IP, поэтому мы будем использовать в своем проекте компонент ServerSocket, с помощью его мы будем принимать сообщения с запросом от браузера. Многие скажут «почему использован компонент, а не при помощи библиотеки WinSock, ведь это будет быстрее работать, и позволит более гибко управлять соединением», я думаю, что цель этой статьи раскрыть особенности протокола HTTP, а  высокая скорость сервера здесь не самое главное.

Лирику в сторону, надеюсь у вас уже запущен Delphi. Дня начала кинем на форму пару компонентов (“ServerSocket”, ”Memo”). Первый для общения с браузером а второй просто для визуализации запроса браузера.

Программа будет состоять из двух модулей, первый основной, а во втором будут храниться два класса: “TRequest”, “TResponse”. Первый класс содержит информацию о запросе клиента, а второй отвечает за формирование ответа клиенту.

Первый модуль содержит всего две процедуры это обработчик события активации формы и обработчик события “OnClientRead” компонента “ServerSocket”.

Первую процедуру приведем к следующему виду:

В этой процедуре происходит загрузка MIME-стандартов в массив типа записи который содержит поле расширения файла и поле “Content-Type” это поле нужно, что бы при ответе указать браузеру что за данные ему передаются. Например: “Content-Type: text/html”. Файл “mime.types” был позаимствован мною из web-сервера “Apache”.

Создадим обработчик события “OnClientRead” компонента “ServerSocket” и оформим его следующим образом:

В первой строчке переменной “c” присваивается запрос клиента, в следующей строчке этот же запрос отображаем в “Memo” (для повышения скорости можно убрать эту строку). Далее создаем два объекта описание, которых находится во втором модуле. Затем вызывается метод “fill” с параметром всё того же запроса, в этом методе происходит анализ запроса, и заполняются некоторые поля объекта “conte”. Например, в запросе есть поле “Accept” и оно переходит в поле “HTTP_ACCEPT” объекта “conte”, и эта переменная в последствии будет передана сценарию CGI в качестве переменной окружения. Вообще стандартный запрос браузера, если ввести в строку запроса “ http://localhost/forum/ ” выглядит следующим образом:

Следующие две строчки служат для получения имени компьютера(это понадобится нам в дальнейшем).

Далее вызываем метод “fill” но уже объекта “conte” и передаем ему три параметра, это имя хоста сделавшего запрос, его IP адрес и имя компьютера (на этот раз того на котором выполняется сервер).  В данном методе происходит формирование тела ответа посылаемое клиенту.

Следующим оператором мы посылаем заголовок ответа клиенту. А следующим мы посылаем тело ответа.

Далее следует оператор, который закрывает соединение (это очень важно, иначе браузер будет ждать ещё информации).

Ну и последние два оператора уничтожают объекты.

Ну что ж самое страшное позади, теперь осталось только разобрать устройство классов, итак перемещаемся в модуль №2.

По мере написания этого проекта структура объектов постоянно менялась, и получилось так, что класс “TRequest” содержит всего один метод и не имеет переменных. Итак, рассмотрим его (метод “fill”). Он выглядит так:

Здесь происходит извлечение нужных данных из запроса, пример которого был представлен выше, и заполняются поля объекта “Conte”. Конечно, прямое манипулирование полями объекта не является хорошим стилем программирования, но в данном случае так будет проще. В конце этой процедуры извлекается данные, переданные по методу “POST” так как они передаются сразу после заголовка и отделены от заголовка пустой строкой. В данной процедуре вы можете разобраться сами, так здесь ничего особо важного не происходит.

Далее рассмотрим метод “fill” объекта “Conte”. На вход метода подается три параметра о которых говорилось выше. И выглядит этот метод следующим образом:

Рассмотрим его поближе. Сначала создаем объект “default” класса “TStringList” и загружаем в него файл “default.txt” в котором расположены имена файлов загружаемые по умолчанию если не указано явно имя файла. Далее заполняем некоторые переменные окружения.

Дальше следует условие: “if (Path='') or (Path=' ') then”, где переменная “Path” содержит строчку запроса браузера, и если это условие истинно, то в браузере ввели что-то вроде “http://localhost/”. В данном случае неуказан явно файл который следует загрузить, соответственно нам требуется проверить есть ли в корневой папке файл который следует загрузить по умолчанию, что мы и делаем в цикле “for” от 0 до “default.Count-1”. А в цикле мы проверяем, есть ли файл функцией “FileExists” которая возвращает “true” если файл есть. Итак, если есть файл мы его загружаем в поток, вызываем метод “head” который заполняет поля заголовка HTTP ответа, и используя оператор “Exit” выходим из процедуры так как задача выполнена (файл найден и готов для отправки).

Если условие “if (Path='') or (Path=' ') then” не выполняется то выполняется оставшаяся часть кода, где мы проверяем есть ли тот файл что запросил пользователь, если да, то следующими двумя операторами извлекаем расширение запрашиваемого файла и если оно равно “exe”, то нас просят выполнить CGI-скрипт, для этого следует: вызвать метод “set_Env_Var” класса “TResponse”, в переменную “nam” поместим имя запрашиваемого файла без расширения, эта переменная потребуется нам при вызове пользовательской процедуры “RunCaptured”. Подробно эту процедуру объяснять не буду, расскажу лишь смысл, а смысл её такой: запускаем CGI-скрипт, подаем ему на вход данные, если вызов был по методу “POST”, и считываем из стандартного потока вывода в файл “nam+'.tmp'”. Ну а далее всё стандартно, загружаем этот файл (nam+'.tmp') в поток, вызываем “head” и наконец удаляем два временных файла.

В случае же если файл не “exe”, то мы перебираем массив “mim”, если помните, мы его заполняли в начале статьи. И когда мы находим нужное нам расширение то вновь поступаем стандартно, только в качестве второго параметра метода “head” отправляется “mim[i].cont_t”, что бы браузер знал что ему пришло, и был готов запустить нужное приложение или предложить скачать файл.

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

И наконец если это даже не папка (какая наглость) то отправляем браузеру 404-ую ошибку что означает “file not found” (для особо продвинутых “файл не найден”).

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

Если немного напрячься то к этому серверу можно прикрутить поддержку “PHP” и “Perl” ну или ещё чего-нибудь (в том числе и собственных интерпретаторов).

Исходный код примера можно скачать тут, а программу тут.

Сайт создан в системе uCoz