Пишем движок сайта на Lazarus

Несколько лет назад где-то в Сети мне встретилась заметка, посвящённая CMS, написанной на Delphi. Понятно, всё это дело могло крутиться исключительно под Windows, что в сфере Web скорее исключение (Linux и BSD рулят).
Но самая идея написания сайто-движка на «нестандартном» языке показалась мне забавной. И вот давеча решил я немного покопать в эту сторону. Из академического интереса так сказать. А что? Камрад, вон, пилит 8-битный процессор паяльником.

Для начала немного теории. Давным-давно (году этак в 1993-м), когда статичный HTML всем уже порядком наскучил, а всякие PHP ещё не родились, придумали умные люди забавную штуку под названием CGI. В детали углубляться не буду — люди вы грамотные, сами нагуглите. Нам же важен один факт — силами CGI на сервере можно выполнить программу практически на любом языке — будь-то Pascal или вообще VisualBasic. Главное, чтобы программа была откомпилирована в формате, подходящем для ОС на конкретном сервере. Таким образом, кстати, пилят сайты на Python (интерпретатор Python выполняется как модуль CGI).
Когда народ попривык к шустрому интернету, скорости CGI стало маловато, и другие умные люди придумали FastCGI — почти тот же самый CGI, но типа весь из себя fast. Фишка в том, что FastCGI не плодит сущности, и крутит каждую программу в одном потоке.

С написанием программа под CGI заморочек ровным счётом никаких — юзай себе стандартный вывод в консоль, и будет тебе счастье. Для Pascal, например, будет как-то так:

program first;
begin
  writeln('Content-type: text/plain');
  writeln;
  writeln('Hello, world!');
end.

Передали серверу заголовок и увидели в браузере старый-добрый Hello, world!.

С FastCGI всё немного интереснее, ибо он юзает не консоль, а самые, что ни на есть Socket’ы (ну, или вообще голый TCP/IP). И вот тут уже есть небольшая заморочка — писать под FastCGI можно на любом языке с одним условием — под рукой должна быть реализация Socket’ов или (в идеале) — API для работы с FastCGI.

Я, как можно догадаться, писать недо-сайт буду на реализации Pascal, известной как FreePascal. Более того, возьму аж цельный Lazarus, дабы не отставать от коллег, упомянутых в самом начале статьи.

Но для начала давайте организуем небольшой сервачок для своих бесчеловечных опытов. Для начала поставим Apache. Я работаю под Windows 7, так что качал win-сборку версии 2.4.3. Затем цепляем к Apache модуль FastCGI. Я взял реализацию mod_fcgid с сайта самого Apache.

Чтобы организовать виртуальный хост с поддержкой FastCGI, нужно немного подправить конфиг Апача (c:Apache24confhttpd.conf).

Нас интересуют две строчки:

LoadModule fcgid_module modules/mod_fcgid.so
LoadModule rewrite_module modules/mod_rewrite.so

Первую придётся в конфиг добавить, со второй — убрать # в начале строки. Собственно, mod_rewrite нам понадобится позже, но чтобы два раза не ходить, правим сразу.

Ну, и собственно, создаём хост с FastCGI (добавляем указанные строчки в конец файла httpd.conf):

<Directory "c:/Apache24/fcgi-bin">
    AllowOverride None
    Options None
    Require all granted
</Directory>

<Directory c:/Apache24/fcgi-bin/>
 SetHandler fcgid-script
 Options +ExecCGI
 Order allow,deny
 Allow from all
</Directory>

Теперь при обращении к приложениям, расположенным в каталоге c:/Apache24/fcgi-bin Apache будет отдавать браузеру результат их (приложений) работы. Например, если мы напишем программу test.exe и откроем в браузере ссылку http://localhost/test.exe — увидим результат работы test.exe.

Но вернёмся к Lazarus (надо же, наконец, написать тот самый test.exe). Как ни странно, средства для разработки CGI и FastCGI в его составе уже есть. Чтобы из задействовать — устанавливаем набор компонентов fpWeb из папки c:lazaruscomponentsfpweb. Теперь в IDE появились шаблоны проектов FastCGI. Воспользуемся: Файл -> Создать. В появившемся окне ищем Приложение FastCGI.

Вуаля! Заготовка FastCGI-приложения готова. Осталось создать обработчик события OnRequest для компонента TFPWebModule1. Например, такой:

AResponse.ContentType := 'text/html;charset=utf-8';  
AResponse.Contents.Add('Hello World!');

Но, как вы понимаете, тупо выводить текст по прямой ссылке — это как-то… хм… статично. Нафига же нам тогда FastCGI вообще? Давайте сделаем прототип работы с ЧПУ. Для начала снова поправим конфиг Apache. Точнее — добавляем в его конец такие строки:

<IfModule mod_rewrite.c>
 RewriteEngine On
 RewriteRule ^(.*)$ c:/Apache24/fcgi-bin/pascalpress?$1 [T=application/x-httpd-cgi,L]
</IfModule>

Попутно настраиваем проект в Lazarus таким образом, чтобы при компиляции получать файл с именем pascalpress (без расширения!).

Теперь пишем в обработчике OnRequest такой код:

{ TFPWebModule1 }

// Базовая функция (выполняется каждый раз при обращении к скрипту)
procedure TFPWebModule1.DataModuleRequest(Sender: TObject; ARequest: TRequest; AResponse: TResponse; var Handled: Boolean);

 // вывод в текст страницы (просто для удобства - закос под PHP)
 function echo(str: string): boolean;
 begin
   AResponse.Contents.Add(str);
 end;

var
  i: integer;
   lData: String;
begin
 // задаём тип контента - текст/html (для графики могут понадобиться другие типы)
 AResponse.ContentType := 'text/html;charset=utf-8';

 // корневой каталог сервера
 WWW:=ExtractFilePath(ParamStr(0));

 // если нет параметров (т.е. загружается главная страница сайта)
 if (ARequest.QueryFields.Count<=0) or (ARequest.QueryFields[0]='/') then
  begin
    // грузим index.html
    echo(WWW+'/index.html');
    AResponse.Contents.LoadFromFile(WWW+'/index.html');
  end
 // иначе - разбираем параметры
 else
  begin
   // параметры URL (ЧПУ вида mysite.ru/1/2/3) дадут 1 параметр вида /1/2/3)
   // использовать в запросах ? нельзя - всё, что идёт после него, отрезается
   for i:=0 to ARequest.QueryFields.Count-1 do
    begin
     // если есть файл с именем, равным имени ключа, грузим его целиком
     if FileExists(WWW+ARequest.QueryFields[i])
      then AResponse.Contents.LoadFromFile(WWW+ARequest.QueryFields[i]);
    end;
  end;

  // типа закончили формирование ответа
  Handled := True;
end;

Получить данные от пользователя тоже очень просто:

var
email: string;
...
email:=ARequest.ContentFields.Values['email']

Как можно догадаться, этот код позволяет получить значение поля ввода с именем email.

Собственно, на этом можно введение в написание CMS на Lazarus закончить. Что мы имеем в результате? Наша программа принимает на входе URL (немного извращённо, зато надёжно). При этом наличие самой программы скрывается. Фактически, если использовать в URL расширения .php или .html — посетители сайта вообще не заподозрят, что сайт крутится не на типичном скриптовом языке. Также мы можем получить данные из полей ввода.
Осталось прикрутить БД, организовать загрузку файлов на сервер — и путь к написанию простейшей CMS открыт. Ну, и ещё нужно всё это дело откомпилировать под Linux или BSD, чтобы можно было запустить на нормальном хостинге. В теории, даже Apache не обязателен — FastCGI можно под nginx завести. Если будет не лень, ещё немного покопаю эту тему.

PS. Желающие могут скачать исходники и готовое FastSGI-приложения из примера, собранное под Windows.

Обновлено 9 марта 2013 года

После беглого изучения вопроса определил для себя плюсы и минусы fpWeb против PHP (с последним немного знаком — на уровне правки чужого кода под свои нужды и написания скриптов средней руки):

+ скорость обработки данных. PHP не заточен под обработку данных, хотя с массивами там работать удобно (именованные массивы — вещь, которой в Pascal’е нативно нет 🙁 ). Плюс есть немало библиотек под специфические задачи (математика, графика и т.п.). На PHP тоже немало всего есть, но вещи, отличные от стандартных, обычно гемморойно делаются. Всё — имхо, возможно я просто плохо знаю PHP.
+ обработка исключение / защита от дурака. Может я плохо знаю PHP (или какие-то его специализированыне библиотеки), но мне показалось, что Pascal в этом плане имеет весомое преимущество. Конечно, выполнение приложения в «вечном цикле» FastCGI усложняет жизнь (как правильно заметил SSerge). Но в целом это решаемо. Кодом на Паскале управлять проще, поскольку он более предсказуем. PHP-скрипт «повесить» на порядок проще (собственно, если бы сервер такие скрипты не «гасил» при превышении лимита времени — не был бы PHP так распространён).
+ Контроль за расходом памяти. Изначально интерпретатор PHP ест меньше памяти, чем Pascal-CGI, но тут надо учитывать, что всё может измениться при высокой нагрузке и если в случае Pascal расход памяти сильно зависит от качества кода, то в PHP, как мне кажется, этим делом управлять труднее (если, допустим, интерпретатор PHP при выполнении определённых языковых конструкций кушает определённый объём памяти, и с ростом количества подключений этот объём растёт, вы, скорее всего, ничего с этим поделать не сможете; а код на Pascal всегда можно переписать; понятно, что и на PHP код можно переписать, но в общем и целом нет гарантий, что вы не упрётесь в косяки интерпретатора, которые обойти не удастся).

— Необходимость более строго следить за кодом в плане проверки значений переменных и т.п., чтобы не вызвать фатальный сбой в приложении (что может повесить сервер) — здесь нет надзирателя в виде интерпретатора PHP, который за вами приберёт. С другой стороны, хороший программист должен из без «указки сверху» следить за своим кодом 🙂
— Сложность в развёртывании на типовых хостингах. Не все хостеры разрешат свободно запускать FastCGI-приложения. С другой стороны, при грамотной настройке серверного ПО FastCGI может быть заметно безопаснее, чем PHP, поскольку его можно «изолировать» от остальной части сервера (отдельный chroot).
— Сложность с правкой кода «на лету» — вам каждый раз придётся компилировать приложение и заливать его рабочий сервер; а для этого придётся временно останавливать его выполнение, что на рабочих сайтах/сервисах не есть хорошо. Возможно, это можно как-то обойти, но я пока не придумал (плохо знаю фишки Apache/nginx).

Пока как-то так.

6 комментариев

  1. Ответить

  2. От Andrey

    Ответить

  • От Nik

    Ответить

    • От Andrey

      Ответить

      • От Nik

        Ответить

  • От Дмитрий

    Ответить

  • Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *