Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Программирование для начинающих и не только Веб-сервер своими руками


Информационный Канал Subscribe.Ru

Веб-сервер своими руками

Эта статья предназначена для тех, кто как и я не хочет тратить дорогие Интернет-минуты для отладки своих проектов, тем более, если в них используется 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; , где AThread - поток, который содержит информацию о подключении. Параметр RequestInfo - содержит информацию о запрашиваемых данных. ResponseInfo - используется для передачи результата выполнения запроса.

Для проверки работоспособности нашего сервера, мы создадим файл(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);

Настройка браузера предельно проста. Надо просто указать в настройках прокси сервера название компьютера на котором находится сервер и соответствующий порт(я сам использую 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;
Но просто передать значения 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;
Порядок работы:
  • Сначала мы создаем файл("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
Отписаться
Убрать рекламу


В избранное