Быстрейшая CMS на Delphi для IIS как ISAPI Extension

Почему вы пишите сайты на скриптах, вроде 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

Итак, приступим к работе. На Delphi. Я буду использовать Delphi 2010 и IIS 6 под Windows Server 2003 R2 EnUS. Почему Делфи? В последнее время на хабре много пишут о том, что с этим языком что-то не так, а CMS подразумевает работу с базой данных, почтой и множеством текстовых строк (привет, TStringList). Вот и посмотрим, кто дурак.

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

image

Вот и все, готова ваша ISAPI Extension. Пока она ничего не возвратит Web серверу конечно, но если ее откомпилировать она готова к работе.

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

image

Сделайте все, как на картинке и давайте уже двигаться дальше!

image

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

Создадим новый класс — TWebEngine в файле WebEngineClass. Он будет у нас отвечать за все манипуляции с данными. Окружение — классы 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 запроса вида

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 (остальные страницы привилегированные, у них у каждой свой собственный метод).

Ах, да. Что такое 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. Магия.

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

Все, с кодом закончили. Самые важные аспекты я осветил, теперь как же запустить это все на отладку? Не как обычное приложение, зеленая кнопка тут бессильна (сперва). Для 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): ISPI habr DEMO v1


2 комментария

avatar
  • 1mho
  • 0
огромное спасибо.
avatar
Хорошая статья. Спасибо!
Но картинки не отображаются и код проекта не скачивается :(
Пробовал на разных браузерах.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.