|
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
2004 г. Веб-сервер своими рукамиМихаил Продан,,
Как известно, все уже написано до нас. В том числе и веб-серверы на любой вкус. С другой стороны, наш собственный веб-сервер все равно будет обладать несомненным преимуществом - мы сможем контролировать его код и, по мере необходимости, добавлять новые возможности Лирическое отступлениеКогда мой сайт обитал на boom.ru, с отладкой все было в порядке: изменяешь в блокноте, переходишь в Оперу, жмешь F5 - и все изменения налицо. Но когда я начал переходить (и пока еще, правда, не перешел) на h10.ru с его возможностями PHP, Perl… - с отладкой начались проблемы. Как вам наверняка известно, PHP и Perl исполняются не на стороне браузера, а на стороне сервера. А поскольку Apache из диска "К + П" № 3 я так и не установил (мой CD-ROM приказал долго жить), была предпринята попытка написать собственный веб-сервер (естественно, на Delphi). Эта статья предназначена для тех, кому, как и мне, не хочется тратить дорогие интернет-минуты на отладку своих проектов (тем более если в проектах этих используется PHP или другие CGI-средства). С чего начатьНачнем мы, как всегда, с запуска нашего Delphi. После появления формы перебросим из палитры компонентов TIdHTTPServer (который, по большому счету, и будет выполнять за нас всю грязную работу по соединению с клиентом и общению с ним). Нам же останется лишь переадресовывать запросы клиента на соответствующие файлы. Кроме того, я порекомендовал бы также перетащить на форму еще и TButton. И в его реакции на нажатие написать код запуска нашего сервера: procedure TForm1.Button1Click (Sender: TObject); begin Self.IdHTTPServer1.Active:=True; end; , а в событие Form1.OnDestroy (): procedure TForm1.FormDestroy (Sender: TObject); begin Self.IdHTTPServer1.Active:=False; end; Теперь поподробнее рассмотрим событие idHTTPServer.OnCommandGet, которое имеет тип:
TIdHTTPGetEvent = procedure (AThread: TIdPeerThread;
RequestInfo: TIdHTTPRequestInfo; ResponseInfo:
TIdHTTPResponseInfo) of object;
Где:
Для проверки работоспособности нашего сервера создадим файл (Response.txt) примерно следующего содержания: <html> <head> <title>Testing</title> </head> <body> <h1>This is the test</h1> </body> </html> А в обработчике события idHTTPServer1.OnCommandGet напишем такой код:
ResponseInfo.ContentStream:=TFileStream.Create
('D:\Response.txt',fmOpenRead);
После запуска сервера открываем наш уже настроенный браузер и набираем любой адрес. В результате мы должны получить то, что было написано в файле 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/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… В этой главе мы остановимся именно на разработке поддержки Standalone CGI. Итак. 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; Где:
Результат: True - если приложение нормально напустилось, и False - в противном случае. Для переадресации устройства вывода воспользуемся параметром lpStartupInfo, который имеет следующую структуру (см. таблицу 1).
Как же все это будет выглядеть в программе? Для начала приведу функцию, которая возвращает в параметре 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;
Но просто передать значения Result в CreateProcess нельзя - для этого используем еще одну сервисную функцию: 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; Порядок работы:
После выполнения этой функции возвращаемое значение передаем в ResponseInfo.ContentText. А как же PHP, Perl…
Значит, для того чтобы научить наш сервер работать с PHP, надо, во-первых, скачать пакет PHP, прописать необходимые пути к нему в переменной PATH. В код сервера добавить фильтрацию по расширению запрашиваемого документа (php, php3, php4) и передать эти файлы в качестве параметров (вспомните параметр lpCommandLine) на обработку CGI'шке php.exe. Результат, как и прежде, следует вернуть в ContentText. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
CITForum © 1997–2025