SV Foster ([info]sv_foster) wrote in [info]ru_delphi,

ISAPI Extensions for IIS or How to write CMS on Delphi

Статью завернули на хабре, несмотря на то, что ничего подобного на нем никогда не было. Очевидно, людям интересны новости про карты городов под андроид, обращения людей из википедии, разбор журнала Вог на части и другие блондинистые занятия. Или не нравится им Delphi. Я не знаю, в общем, а статью не хочется просто так выкидывать, она переселяется в LJ сообщество ru_Delphi.

Introduction


Почему большое количество Web разработчиков пишут сайты на скриптах, вроде PHP? Я, лично, не понимаю и использую другой способ - ISAPI Extensions for IIS.

image

ISAPI Extensions (расширения Web сервера) - простая DLL библиотека с парой функций, которые обрабатывают запрос пользователя к Web серверу и возвращают ответ. Точнее - одна функция, остальные служат для регистрации/разрегистрации DLL в рабочем процессе IIS.

Из этого следует, что, по сравнению с другими способами, такими, как скрипты, ISAPI Extensions выигрывают по:
  1. Скорость работы. DLL загружается в рабочий процесс IIS и находится в памяти всегда, а не читается с диска при каждом обращении. Не требуется интерпретатор языка - код нативный и исполняется непосредственно процессором;
  2. Функционал. Вы можете использовать любую другую DLL или любой вызов WinAPI. Теоретически возможно специально сформированным http запросом отформатировать диск
  3. Защищенность. Ваш код очень сложно проанализировать и понять, как взломать сайт - нативный код человеком плохо читается
  4. У вас есть нормальный отладчик во время разработки! Самый вкусный момент. Можете делать step-by-step trace, стэк и память доступны для анализа.

Есть и некоторые ограничения, как и везде, но их сложно назвать недостатками:
  1. Требуется IDE для разработки;
  2. Невозможно просто взять и поправить 2 строчки в скрипте на сервере - требуется пересобрать проект, как любое Windows приложение;
  3. Вероятность завалить рабочий процесс IIS с ошибкой Acceess violation at address (не бояться, он просто перезапустится);
  4. Различные security violations, связанные с возможностью делать, что хочешь. Хотя, это уже точно вопрос к админам, как они раздадут права на сервере.
  5. Ваши друзья - php кодеры вас не поймут и назовут дураком, если вы передадите им такую CMS. Еще они не смогут ее установить :D


Your first ISAPI Entension


Итак, приступим к работе. Я буду использовать Delphi 2010 и IIS 6 под Windows Server 2003 R2 EnUS.

Добрые Борландны, еще много лет назад, упростили процесс работы с ISAPI до предела. Чтобы начать выберем File -> New -> Other и выберите WebBooker -> Web Server Application. Далее кликаете ISAPI/NSAPI Dynamic Link Library и нажмите OK.

image

Вот и все, ваша первая ISAPI Extension готова. Пока она ничего не возвратит Web серверу конечно, но если ее откомпилировать, то она готова к работе. Компилировать нужно, нажимая не зеленую стрелочку, а Ctrl+F9.

Переключитесь на Unit1 и выберите Action Editor для объекта WebModule1. Об экшенах поговорим в другой раз, пока нужно просто создать default action, который будет обрабатывать все наши запросы.

image

Приведите внешний вид свойств объекта, как на картинке:

image

У этого экшена есть события, как вы уже догадались. А точнее - одно. Думаю, без картинки не заблудитесь и поймете, что надо сделать (если что, в конце поста есть ссылка на исходники).

Web Engine Class


Создадим новый класс - TWebEngine в файле WebEngineClass.pas. Он будет у нас отвечать за все манипуляции с данными. Окружение - классы TOptions и TTempVariables. TOptions будет хранить данные о базе (с которой мы будем соединяться в следующий раз), а временные вариаблы - информацию о расположении DLL библиотеки на диске и ее имени.

Сервер Microsoft IIS передает нашей библиотеке множество параметров и хочет, чтобы мы ему что-то вернули после работы. Эти объекты представляются как Request: TWebRequest и Response: TWebResponse, которые прописаны как property класса TWebEngine. Просто передадим их в класс в коде экшена:


  try
    try
      WebEngine.Request:= Request;
      WebEngine.Response:= Response;
      WebEngine.Process;
    finally
      WebEngine.Free;
    end;
  except
    on E: Exception do
      Response.Content:= 'Internal error. Low lavel problem! ' + E.Message + ' Contact us noone@nowhere.com';
  end;
 



Логика кода, надеюсь, ясна - создать класс, передать параметры, запустить обработку (process) и удалить (free) класс. Если что - выдать ошибку.

Разъясняя код метода (да да, именно метода. Функции и процедуры одним словом называются метод) Process хочу в первую очередь указать на то, как разбирается для нас строка параметров http запроса вида

http://nowhere.com/?pagename=main&language=en

все параметры попадают в Request и могут быть оттуда легко получены вот так:

LocalOptions.Language:= Request.QueryFields.Values[ 'language' ];
LocalOptions.TypeOfPage:= Request.QueryFields.Values[ 'pagename' ];



То есть никаких шаманств не требуется, хотя возможны при желании!

Код метода Process устанавливает страницей по умолчанию страницу main с языком EnUS (код 1033) и запускает дальнейшую обработку:


  // language
  // default language is EnUS
  LocalOptions.Language:= 1033; // EnUS

  // other languagues
  if AnsiSameText( Request.QueryFields.Values[ 'language' ], 'En' ) then
    LocalOptions.Language:= 1033; // EnUS
  if AnsiSameText( Request.QueryFields.Values[ 'language' ], 'Ru' ) then
    LocalOptions.Language:= 1049; // RuRu
  // type of page
  // dafault is help page
  if Length( Request.QueryFields.Values[ 'pagename' ]) <= 0 then
    LocalOptions.TypeOfPage:= 'help'
  else
    LocalOptions.TypeOfPage:= Request.QueryFields.Values[ 'pagename' ];

  try
    try
      if LocalOptions.TypeOfPage = 'info' then
        Begin
          PageInfoShow;
          Exit;
        End;

      if LocalOptions.TypeOfPage = 'main' then
        Begin
          PageMainShow;
          Exit;
        End;

      // common page
      PageCommonShow;
    except
      on E: Exception do
        Begin
          PageResponse.Clear;
          PageResponse.Text:= 'Internal error ' + E.Message;
        End;
    end;
  finally
    Send( PageResponse.Text );
  end;
 



Код, в силу красоты языка Delphi, не требует комментариев вообще, если вы конечно знаете хоть немного этот самый EnUS.

Страница info - чудесная страница отладки, если на нее зайти можно посмотреть все переменные, что бзаузер посылает серверу.

Страница help - просто какае-то страница на сервере. Обрабатывается PageCommonShow (остальные страницы привилегированные, у них у каждой свой собственный метод).

Non-ANSI Encoding Problem


Ах, да. Что такое PageResponse? Это не объект сервера IIS, это собственный объект класса ВебДвигателя. Тут я создал лишнюю работу, потому что, вы не поверите, но TWebResponse не умеет возвращать странички в UTF-8 или любой кодировке, кроме ANSI. По этому, пока идет обработка, весь код странички копится в объекте PageResponse типа TStringList (в кодировке UCS2LE) и перекодируется во время вызова Send( PageResponse.Text ). Это - самый главный секрет написания ISAPI Extension на Delphi, теперь и вы его знаете.

Давайте посмотрим код метода Send поближе:


 // encoding the string USC2LE -> UTF8
  BufferUTF8String:= UTF8Encode( s );
  // setting BOM
  BOM[ 0 ]:= 239;
  BOM[ 1 ]:= 187;
  BOM[ 2 ]:= 191;

  try
    Stream:= TMemoryStream.Create;
  except
    on E: Exception do
      Begin
        LocalOptions.Response.Content:= 'Error 0x1, TMemoryStream.Create @ TWebEngine.Send ' + E.Message;
        Exit;
      End;
  end;

  try
    try
      // writing BOM
      Stream.Write( BOM, SizeOf( BOM ));
      // writing content
      Stream.Write( BufferUTF8String[ 1 ], Length( BufferUTF8String ));
      Stream.Position:= 0;

      // setting content type
      LocalOptions.Response.ContentType:= 'text/html; charset=UTF-8';
      LocalOptions.response.ContentEncoding:= 'UTF-8';

      // sending
      LocalOptions.Response.ContentStream:= Stream;
      LocalOptions.Response.SendResponse;
    finally
      // DO NOT Stream.Free here!
    end;
  except
    on E: Exception do
      Begin
        LocalOptions.Response.Content:= 'Error 0x2 @ TWebEngine.Send ' + E.Message;
        Exit;
      End;
  end;
 



Здесь происходит очень простая вещь - текст перекодируется, записывается в поток и отсылается, как массив байт. Пользователь видит страничку в UTF-8. Магия.

Page Producer


Еще, о чем можно поговорить это TPageProducer. Это замечательный класс, который преобразует тэги вида <#IAmTag> в шаблонах страницы на любой текст. Происходит это в PageProducerOnHTMLTag - вызывается каждый раз при нахождении такого тэга. Для примера я создал тэг #HeaderCopyright, код которого располагается в файле, и тэг #CopyrightYearsRange, который генерируется в самом ISAPI Extension. Посмотрите исходник, не буду останавливаться на этом моменте, он того не стоит.

How To Debug?


Все, с кодом закончили. Самые важные аспекты я осветил, теперь как же запустить это все на отладку? Не как обычное приложение, зеленая кнопка тут бессильна (сперва). Для DLL нужен хост-процесс.

Сперва остановите все службы IIS: net stop IISAdmin в command prompt и Y что вы останавливаете дочерние службы.

image



Запустите IISAdmin, без дочерних служб: net start IISAdmin.

В Delphi, в свойствах проекта поставьте папку бинарников - папкой публикации IIS узла, включите debug symbols и вообще сделайте, как на картинках:

image

image

image



В самом IIS, если еще не сделали - страница по умолчанию index.dll, ISAPI Extension разрешить в Web Server Extensions и свойствах конкретного узла.

Теперь, по нажатию зеленой кнопки в Delphi запуститься хост-процесс IIS и заработает отладка. Вот, например, нет файла:

image



А вот что видет пользователь:

image



Самое вкусное - отладка работает, как в обычном Windows приложении! Всем php разработчикам - завидовать.

image



Удачи вам, это все, на первый раз. Далее поговорим о соединении с базой данных из ISAPI Extensions.

Код проекта (Delphi 2010): ISAPI DEMO v1

© SV Foster, 2011. SV@novicorp.com

  • Post a new comment

    Error

  • 18 comments

[info]_ado_

January 21 2011, 12:19:17 UTC 1 year ago

Интересно, но всетаки под кат!

[info]sv_foster

January 21 2011, 12:24:12 UTC 1 year ago

Да, конечно )

[info]icyh

January 21 2011, 12:24:10 UTC 1 year ago

Спасибо, очень интересно.

[info]megaroy

January 21 2011, 12:50:47 UTC 1 year ago

спасибо, интересно. Не понял только как формируются данные (ответы в html) для юзера.

ЗЫ Но всё же, бесплатный LAMP - это хороший двигатель веб-прогресса :)

[info]sv_foster

January 21 2011, 12:54:53 UTC 1 year ago

Они кладутся в PageResponse (:TStringList), как html код. А уж откуда вы их положите - из файла или базы дело ваше.

Скачайте пример, но полный и поможет лучше понять механизм работы.

Apache тоже поддерживает ISAPI. Только вам придется RTFM что понять, как заставить его это делать.

[info]iandarken

January 21 2011, 13:13:47 UTC 1 year ago

Здорово, полезная статья. А то все дельфи-ресурсы старые нынче почти повымерли или заброшены. Где тут плюсовать надо?)

[info]dreamwalk3r

January 21 2011, 13:25:58 UTC 1 year ago

Если бы еще было много хостеров, у которых серверы под виндой и такое разрешают.. )
А так - спасибо за статью.

[info]oldroader

January 21 2011, 14:16:40 UTC 1 year ago

Собственный сервер иметь надо.

[info]keenest

January 21 2011, 14:46:04 UTC 1 year ago

а что с апачем например?

[info]sv_foster

January 21 2011, 15:39:31 UTC 1 year ago

Знаю только, что возможно использовать ISAPI на апаче. Дальше придется вам читать инструкцию.

[info]maravan

January 21 2011, 18:24:44 UTC 1 year ago

прикольно

[info]berrymorr

January 24 2011, 03:47:04 UTC 1 year ago

понравилось, забавно!)
у способа, правда, главный недостаток в том, что дедикейтед нужен, виртуальный хотя бы. с другой стороны, цены на них щас такие, что там можно даже хомяка держать.

[info]sv_foster

January 24 2011, 10:07:06 UTC 1 year ago

Кажется, с Windows 2008 не нужен. Там можно управлять IIS'ом нормально. Да и в 2003 при желании можно сделать...

Colocation сейчас начинается от 800 рублей, думаю дешевле купить его. VPS неоправданно дороже.

[info]_my

March 31 2011, 15:08:49 UTC 1 year ago

А работает такой ISAPI Extension под IIS 7.5 под Windows Server 2008 R2 (64 bit)?

[info]_my

March 31 2011, 15:24:34 UTC 1 year ago

Работает, только нужно в настройках Application Pool установить Enable 32 bit applications :)

Anonymous

December 12 2011, 16:01:41 UTC 5 months ago

Can I share it on Facebook?

I want to share it on my Facebok wall
You can find me there as Bark Pacober

[info]yergakit

February 7 2012, 18:57:11 UTC 3 months ago

Все классно написано

[info]meillakur

February 19 2012, 18:17:09 UTC 3 months ago

Автор продолжай в том же стиле
Create an Account
Forgot your login or password?
Facebook Twitter More login options
English • Español • Deutsch • Русский…