Рассылка закрыта
При закрытии подписчики были переданы в рассылку "О карьере и профессиональном развитии IT-специалистов" на которую и рекомендуем вам подписаться.
Вы можете найти рассылки сходной тематики в Каталоге рассылок.
Программирование для начинающих и не только Веб-сервер своими руками
Информационный Канал Subscribe.Ru |
Веб-сервер своими руками
Эта статья предназначена для тех, кто как и я не хочет тратить дорогие Интернет-минуты для отладки своих проектов, тем более, если в них используется PHP или другие CGI средства.
С чего начать
Начнем мы как всегда с запуска нашего Delphi. После появления формы. Мы перекинем из палитры компонентов TIdHTTPServer. Который по большому счету и будет выполнять за на всю грязную работу по соединению и общению с клиентом. Нам же только понадобиться переадресовывать запросы клиента на соответствующие файлы. Кроме этого я бы рекомендовал также перетащить на форму TButton. И в его реакции на нажатие написать код запуска нашего сервера.
, а в событие Form1.OnDestroy():procedure TForm1.Button1Click(Sender: TObject); begin Self.IdHTTPServer1.Active:=True; end;
procedure TForm1.FormDestroy(Sender: TObject); begin Self.IdHTTPServer1.Active:=False; end;
Теперь детально рассмотрим событие idHTTPServer.OnCommandGet, которое имеет тип:
TIdHTTPGetEvent = procedure (AThread: TIdPeerThread; RequestInfo: TIdHTTPRequestInfo; ResponseInfo: TIdHTTPResponseInfo) of object;
, где AThread - поток, который содержит информацию о подключении. Параметр RequestInfo - содержит информацию о запрашиваемых данных. ResponseInfo - используется для передачи результата выполнения запроса.
Для проверки работоспособности нашего сервера, мы создадим файл(Response.txt) примерно такого содержания:
А в обработчике события idHTTPServer1.OnCommandGet напишем такой код:
<html>
<head>
<title>Testing</title>
</head>
<body>
<h1>This is the test</h1>
</body>
</html>
ResponseInfo.ContentStream:=TFileStream.Create('D:\Response.txt',fmOpenRead);
Настройка браузера предельно проста. Надо просто указать в настройках прокси сервера название компьютера на котором находится сервер и соответствующий порт(я сам использую 800-й).К примеру у меня в IE эти настройки выглядят так

После запуска сервера открываем наш уже настроенный браузер и набираем любой адрес. В результате мы получим то, что было написано в файле Response.txt. Но это нам мало что дает ведь какой бы адрес мы не использовали, результат будет одним и тем же. Для получения чего-то более похожего на веб-сервер. Нам понадобится немножко его доработать:
Что мы здесь делаем:if RequestInfo.Host='www.test.com' then begin ResponseInfo.ContentStream:=TFileStream.Create( 'D:\Projects\HTMLProjects\MySite\'+RequestInfo.Document,fmOpenRead); end;
- Во первых проверяем, какой хост запрашивает клиент.
- Если это "www.test.com", то переадресовываем запрос на соответствующий файл, содержащийся в каталоге с полной копией нашего сайта (D:\Projects\HTMLProjects\MySite).
- Посылаем результат клиенту.
Теперь если в поле браузера ввести строку "www.test.com/index.html", то мы увидим начальную страницу(если она называется index.html) нашего сайта со всеми ссылками, рисунками, скриптами и апплетами.
CGI
Это все хорошо, но что мы с этого имеем. Практически ничего, так как этот же результат мы будем иметь, если наберем в браузере адрес: "D:\Projects\HTMLProjects\MySite\Index.html". И естественно тот аргумент, что набирать "www.test.com/index.html" быстрее не всех устроит (вернее "всех не устроит"). К счастью разрабатываем сервер мы сами, а значит, мы можем внедрять в него всё, что нам угодно: Standalone CGI, WinCGI, ISAPI (NSAPI), Apache CGI, PHP, Perl, Python, MySQL...
В этой главе мы остановимся конкретно на разработке поддержки StandaloneCGI.
Итак. StandaloneCGI - программа работающая под DOS или Windows (и не только, можно и под Linux, только для этого придется перекомпилировать наш сервер). Которая при запуске выдает в устройство стандартного вывода(на экран) всю необходимую информацию. А необходимые параметры передаются ей посредством переменных окружения.
Принцип работы сервера с такими программами таков:
- Задать необходимые переменные окружения
- Запустить программу
- Перенаправить результат из стандартного вывода на другой объект (например в файл)
- Закрытие программы: Закрывается автоматически после вывода всей информации.
- Передать содержимое созданного файла клиенту.
- Удалить файл.
А для того, что б не засорять память всякими ненужными переменными окружения мы воспользуемся функцией запуска приложений CreateProcess - которая перед запуском приложения создает для него свое адресное пространство со своими переменными окружения, которое освобождается после ее завершения.
Прототип этой функции выглядит так:
function CreateProcess(lpApplicationName: PChar; lpCommandLine: PChar;
lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
lpCurrentDirectory: PChar; const lpStartupInfo: TStartupInfo;
var lpProcessInformation: TProcessInformation): BOOL; stdcall;
- lpApplicationName:PChar - название приложения (плюс полный путь к нему)
- lpCommandLine:PChar - командная строка приложения (все параметры, которые передаются приложению через командную сткору)
- lpProcessAttributes, lpThreadAttributes - в нашем случае NIL (подробнее о них читайте Win32 help).
- bInheritHandles:Bool - в наше случае False
- dwCreationFlags - CREATE_NEW_PROCESS_GROUP or DETACHED_PROCESS.
- lpEnvirounment:Pointer - указатель на строку, которая содержит переменные окружения необходимые нашей программе.
- lpCurrentDirectory:PChar - рабочий каталог нашей программы.
- lpStartupInfo:TStartupInfo - параметры запуска приложения
- lpProcessInformation:TProcessInformation - переменная в которую помещаются все дескрипторы запущеного приложения.
- Результат - True, если приложение нормально напустилось, и False в противном случае.
Для переадресации устройства вывода мы воспользуемся параметром lpStartupInfo, который имеет следующую структуру:
Параметр | Описание |
---|---|
cb: DWORD; | Размер данной структуры |
lpReserved: Pointer; | Зарезервировано |
lpDesktop: Pointer; | Для NT - указатель на строку, которая содержит название дисплея, на который вывадится информация приложения (здесь не используется) |
lpTitle: Pointer; | Для консольных приложений строка, которая отображается на панели задач |
dwX: DWORD; | X координата приложения (нам она не нужна - 0) |
dwY: DWORD; | Y координата приложения (нам она не нужна - 0) |
dwXSize: DWORD; | Ширина окна приложения (нам она не нужна - 0) |
dwYSize: DWORD; | Высота окна приложения (нам она не нужна - 0) |
dwXCountChars: DWORD; | Для консольных приложений задает ширину в "текстовых единицах" (для нас 0) |
dwYCountChars: DWORD; | Для консольных приложений задает высоту в "текстовых единицах" (для нас 0) |
dwFillAttribute: DWORD; | Задает сочетание цвета фона и цвета символа (это не для нас 0) |
dwFlags: DWORD; | См. "Описание флагов" |
wShowWindow: Word; | Одна из констант SW_ - режим отображения приложения (это не для нас) |
cbReserved2: Word; | Зарезервировано |
lpReserved2: PByte; | Зарезервировано |
hStdInput: THandle; | Дескриптор стандартного ввода |
hStdOutput: THandle; | Дескриптор стандартного вывода |
hStdError: THandle; | Дескриптор стандартного устройства вывода ошибки |
Описание флагов
Значение | Описание |
---|---|
STARTF_USESHOWWINDOW | Если не задан wShowWindow игнорируется |
STARTF_USEPOSITION | Если не задан dwX, dwY игнорируются |
STARTF_USESIZE | Если не задан dwXSize, dwYSize игнорируются |
STARTF_USECOUNTCHARS | Если не задан dwXCountChars, dwYCountChars игнорируются |
STARTF_USEFILLATTRIBUTE | Если не задан dwFillAttribute игнорируется |
STARTF_FORCEONFEEDBACK | Очень много написано, все равно не использую |
STARTF_FORCEOFFFEEDBACK | Очень много написано, все равно не использую |
STARTF_USESTDHANDLES | Если не задан hStdInput, hStdOutput, hStdError не используются |
Как же это все будет выглядеть в программе? Для начала приведу функцию, которая возвращает в параметре Result:TStringList значения переменных окружения:
procedure CreateServerVariables(RequestInfo:TIdHttpRequestInfo;var Result:TStringList);
begin
if not Assigned(Result) then Result:=TStringList.Create;
Result.Add('HTTP_HOST='+RequestInfo.Host);
Result.Add('REQUEST_METHOD='+RequestInfo.Command);
Result.Add('URL='+RequestInfo.Document);
Result.Add('QUERY_STRING='+RequestInfo.UnparsedParams);
Result.Add('REMOTE_ADDR='+RequestInfo.RemoteIP);
Result.Add('HTTP_ACCEPT='+RequestInfo.Headers.Values['Accept']);
Result.Add('HTTP_USER_AGENT='+RequestInfo.Headers.Values['User-Agent']);
Result.Add('SERVER_PROTOCOL='+sServerProtocol);
Result.Add('SERVER_SOFTWARE='+sServerSoftware);
end;
function FormEnv(Data:TStringList):String;
var i:integer;
begin
Result:='';
if Data<>nil then
begin
For i:=0 to Data.Count-1 do
Result:=Result+Data[i]+#0;
Result:=Result+#0;
end;
end;
function RunCGI(Command:PChar;Data:TStrings):PChar;
var FS:TFileStream;
SI:TStartupInfo;
PI:TProcessInformation;
SL:TStringList;
Env:Pointer;
EnvStr:String;
begin
Result:=PChar(sNoErrorNoResult);
FS:=TFilestream.Create(ExtractFileDir(ParamStr(0))+'\temp.html',fmCreate);
try
FillChar(SI,SizeOf(SI),0);
SI.cb:=SizeOf(SI);
SI.dwFlags:=STARTF_USESTDHANDLES;
SI.hStdOutput:=FS.Handle;
SI.hStdInput:=GetStdHandle(STD_INPUT_HANDLE);
SI.hStdError:=GetStdHandle(STD_ERROR_HANDLE);
EnvStr:=FormEnv(Data);
if not CreateProcess(Command,'',nil,nil,False,CREATE_NEW_PROCESS_GROUP or DETACHED_PROCESS,Pointer(EnvStr),PChar(ExtractFileDir(ParamStr(0))),SI,PI) then
Result:=PChar(sCGIStartError) else
begin
if WaitForSingleObject(PI.hThread,5000)=WAIT_FAILED then
begin
Result:=PChar(sTimeoutError);
exit;
end;
SL:=TStringList.Create;
try
FS.Position:=0;
SL.LoadFromStream(FS);
Result:=PChar(SL.Text);
finally
SL.Free;
end;
end;
finally
FS.Free;
if FileExists(ExtractFileDir(ParamStr(0))+'\temp.html') then DeleteFile(ExtractFileDir(ParamStr(0))+'\temp.html');
end;
end;
- Сначала мы создаем файл("temp.html") в который будем переадресовывать информацию из приложения и обнуляем переменную SI
- Заполняем необходимые поля SI
- Заполняем строку с переменными окружения
- Запускаем наш CGI
- Ждем конца выполнения (5 сек.)
- Передаем результат выполнения в SL, а тот в свою очередь в переменную Result.
- Удаляем файл "temp.html".
После выполнения этой функции возвращаемое значение передаем в ResponseInfo.ContentText.
А как же PHP, Perl?
Возможно кто-то из читателей статьи скажет: "обещал же о PHP, о Perl рассказать: а сам тут всякими CGI'шками мозги пудрит".
Рассказываю: PHP, Perl, как и другие подобные вещи создаются не для одного IIS или PWS. А для использования со многими серверами и на многих платформах. Для примера стандартная поставка PHP (www.ua2.php.net) поставляется в реализациях ActiveScript (т.е. ActiveX), стандартные ISAPI,NSAPI, Apache и Apache2, имеется библиотека для Java, а также в виде StandaloneCGI. Значит, для того, что б научить наш сервер работать с PHP, надо во первых скачать пакет PHP, прописать необходимые пути к нему в переменной PATH. В код сервера добавить фильтрацию по расширению запрашиваемого документа (php, php3, php4) и передать эти файлы в качестве параметров (помните lpCommandLine) на обработку CGI'шке php.exe. Результат как и прежде передать в ContentText.
И что из этого?
Конечно одним Веб - сервером с возможностью работы со StandaloneCGI сыт не будешь, ведь есть еще Win-CGI, ISAPI, NSAPI, Apache, Apache2 и это только для протокола HTTP, а кроме того есть HTTPS, FTP и другие не менее полезные протоколы. Но поскольку Ворлд Вайд Веб (основанный на HTTP) сегодня есть самым популярным, то, возможно, работая именно с Веб - серверами вы сможете вписать своё имя в историю развития мировой информационной сети.
http://subscribe.ru/
E-mail: ask@subscribe.ru |
Отписаться
Убрать рекламу |
В избранное | ||