3-tier

Предисловие

    Как-то давным давно случилось страшное. Наша фирма начала расширяться и открыла филиал. В другом городе. Раньше все работали в одном офисе, в котором была проложена локальная сеть и старенький SQL сервер как-то справлялся с нагрузкой. Здесь и склад и торговля и руководство.
    Проблемы конечно были всегда, но тут они стали быстро переходить в разряд неразрешимых. Как привязать новый офис к информационной структуре фирмы, тем более что руководству хочется видеть что происходит в филиале. И выручку, и складские остатки, и кто кому и сколько должен. Набирать штат программистов в филиал никто особенно не хотел. Устанавливать там сервер и постоянно его обслуживать не хотелось уже нам. Тем более непонятно было как сводить два одновременно работающих предприятия в одно. Выставить наш SQL сервер в интернет мы тоже как-то не решились.
    Вначале подумали создать какое-нибудь WEB приложение и заставить филиал работать с ним через браузер,  тем более что Apache сервер у нас уже был, и какой-никакой опыт работы с html  имелся. Но тут выяснилось, что к браузеру придется привязать сканеры, кассы и весы. Как это делать и во что это выльется мы представить себе не могли.
    Немного подумав, решили что если браузер не может работать с переферией, то нужно написать что-то свое. В таком случае отпадает необходимость создавать сложные и громоздкие web странички, несущие в себе как данные так и кучу разметочной информации с кодом на java-script и можно ограничиться простейшим протоколом связи web-server'а с нашим клиентом. А весь функционал, отображение и взаимодействие компонентов возложить на клиентское приложение. К тому же по возможностям любой нормальный язык гораздо гибче чем java-script. В таком случае нужно создать надстройку над web сервером, реализующую какой-нибудь язык программирования и чтобы разгрузить SQL server, возложить на эту настройку отработку всех алгоритмов и бизнес-правил, а SQL серверу оставить роль которая ему и предназанчена изначально - хранение и выборка данных. Тем более что количество stored-procedures росло лавинообразно да и убогенький полуязык на котором они писались затруднял решение некоторых задач.
    После некоторых раздумий для сервера приложений был выбран mod_perl,  благодаря в основном хорошим отзывам в инете и огромной библиотеке бесплатных модулей. Программу-клиент решили реализовать на Delphi, сказался наш многолетний опыт работы и имеющиеся наработки. Руководство спешило, поэтому раздумывать времени не было.
     В этой статье будут описаны наши первые шаги по созданию распределенной системы. Все примеры сопровождаются полным набором исходников, так что вы сможете либо повторить наш путь, или пойти своим.. Кроме этих примеров скоро будет выложнена для свободного скачивания в исходных кодах небольшая складская система. В будущем, если будет время, мы собираемя добавить информацию по шифрации и сжатию передаваемых данных, аутентификации, развитию протоколов связи и т.д..
    Если вы найдете какие-нибудь ошибки, неточности, или у вас появятся вопросы или преложения - пишите на почту mailto:info@bizonline.ru или на форум http://bizonline.ru/cgi-bin/mwf/forum_show.pl

Установка Web сервера Apache и mod_perl.

Вначале скачайте исходники для apache версии 1.3.xx у нас или у производителя http://apache.rinet.ru/dist/httpd/apache_1.3.31.tar.gz.
Также вам понадобятся исходники mod_perl версии 1.2x - их можно взять или у нас или с сайта разработчиков http://perl.apache.org/dist/mod_perl-1.0-current.tar.gz.
Начинаем работу: разархивируем файлы, производим конфигурацию, сборку, тестирование и установку дистрибутивов:
root> tar -zxvf ./apache_1.3.31.tzr.gz
root> tar -zxvf ./mod_perl-1.0-current.tar.gz
root> cd ./mod_perl-1.29
root> perl Makefile.PL APACHE_PREFIX=/usr/local/apache \
> APACHE_SRC=../apache-1.3.31/src \
> DO_HTTPD=1 \
> USE_APACI=1 \
> EVERYTHING=1

root> make
root> make test
root> make install
    Не обращайте внимание на сообщение Must skip important tests without LWP. Для успешного прохождения этих тестов пришлось бы установить еще несколько пакетов, которые нам сейчас не нужны. Посмотрите размер файла httpd в директории /usr/local/apache/bin/  - он должен составить примерно 600 или более килобайт. Точное значение для конкретного компилятора, платформы и библиотек нельзя предугадать, однако если размер значительно меньше этой величины, то скорее всего что-то прошло неправильно. Чтобы запустить WEB сервер необходимо перейти в каталог указанный в опции APACHE_PEFIX и выполнить команду ./bin/apachectl start.
root> cd /usr/local/apache
root> ./bin/apachectl start
    Теперь запустите свой любимый браузер и наберите http://localhost. Если все произошло без ошибок, вы должны увидеть стартовую страницу вашего сервера.
Кроме этого нам понадобится библиотека, поддерживающая объект Request, который позволяет облегчить разбор html заголовков.
    Ее можно скачать с сайта CPAN или у нас. Разархивируем, конфигурируем, устанавливаем:
root> tar -zxvf ./libapreq-1.3.tar.gz
root> cd ./libapreq_1.3
root> perl ./Makefile.PL
root> make
root> make install

Настройка httpd.conf

    Для продолжения экспериментов нам необходимо настроить файл конфигурации Apache httpd.conf. Обычно он находится в каталоге  /usr/local/apche/conf.  Откройте его с помощью редактора (предварительно создав копию). В первую очередь поместите в него строку:
PerlFreshRestart On
    Apache + mod_perl очень любит кешировать откомпилированные программы, поэтому даже после перезапуска Apache, изменения, внесенные в текст модулей могут не обновиться в кеше и придется делать перезапуск  ОС. Эта строка как раз и заставляет Apache обновлять модули при каждом рестарте. Вообще говоря обновление кеша после изменения текста модулей достаточно неприятная проблема, т.к. нужно не забывать давать команду apachectl restart после каждого изменения. Как частичное решение можно предложить использование специальных модулей вроде Apache::Reload (исходники и документация), или применять require вместо use на время отладки, однако и это не спасает при изменении первичных модулей-обработчиков. Так что отдельная открытая  консоль с командой apachectl reload  в буфере - самое простое и надежное решение.
    Существует несколько способов использования mod_perl - например в режиме эмуляции CGI-вызовов, для чего служат модули Apache::Registry или Apache::Run. Однако они только эмулируют CGI и при этом теряются многие преимущества mod-perl. Для уменьшения времени реакции системы и повышения быстродействия авторами mod_perl рекомендуется создание специальных модулей-обработчиков.

    Теперь немного теории. Для того чтобы сервер выполнил ваш модуль необходимо чтобы:
    Предположим,  что основным каталогом, относительно которого будет происходить поиск исполняемых модулей будет стандартная директория Apache /usr/local/apache/cgi-bin. Создадим в ней еще один каталог ./ThreeTier в котором и будем создавать файлы модулей. Для этого выполним следующие команды:
root> cd /usr/local/apache/cgi-bin
root> mkdir ./ThreeTier

    Теперь нам необходимо передать серверу информацию об этом каталоге. Точнее эта информация нужна не серверу, а Perl, который в момент старта сервера должен добавить в хеш %INC информацию о каталогах в которых нужно искать исполняемые файлы. Например, если серверу потребуется найти мoдуль ThreeTier::ExampleOne.pm, он должен начать поиск с /cgi-bin, найти в нем каталог ThreeTier и, уже там считать и выполнить файл ExampleOne.pm. Для решения этого вопроса нужно использовать опцию  PerlRequire  в httpd.conf. Аргументом этой опции служит абсолютный путь к программе Perl, которая запускается один раз при старте сервера. Поскольку указывается абсолютный путь, то не имеет особого значения где расположен и как называется файл программы. В этом примере мы назовем его PerlRequire.pl поместим в каталог cgi-bin/ThreeTier вместе с модулями. В самом же файле вызовем директиву use lib для указания начального каталога поиска модулей. Кроме назначения начального каталога этот файл может служить для предзагрузки некоторых модулей Perl, стандартных, или созданных программистом. Например для работы с базами данных, или взаимодействия с Apache.
    Итак создаем файл /usr/local/apache/cgi-bin/ThreeTier/PerlRequire.pl.

#!/usr/bin/perl
use Apache                          # Предзагрузка модуля из стандартных путей Perl
use lib '/usr/local/apache/cgi-bin  # Добавление дополнительного пути поиска

1;                                  # Стандартный код возврата

    Назначаем права доступа для этого файла. Владелец - пользователь от имени которого запускается Web сервер, в данном случае nobody. Права для пользователя - только чтение и исполнение.
root> cd /usr/local/apache/cgi-bin/ThreeTier
root> chown nobody ./PerlRequire.pl
root> chmod 500 ./PerlRequire.pl
    Можно конечно принять и другую политику безопасности, тем более для примера, главное чтобы посторонний пользователь не смог изменить этот файл.
    Теперь добавляем следующую строку в httpd.conf:
PerlRequire /usr/local/apache/cgi-bin/ThreeTier/PerlRequire.pl
    После перезапуска Apache (apachectl restart) любой поиск модулей будет происходить в том числе и относительно .../cgi-bin. Кроме того уже на этапе старта будет загружен и откомпилирован модуль Apache и все связанные с ним модули, расположенные в стандартных путях поиска.
    Наш сервер готов к тому чтобы мы написали и зарегистрировали наш первый пример.

Пример №1

Сервер

    В качестве первого примера  попробуем создать простейший эхо сервер. Он будет принимать произвольную строку по команде POST  и возвращать ее клиенту. Везде далее будет использоваться только метод POST. Метод GET весьма ограничен как по длине передаваемой строки, так и по набору символов, которые он в состоянии принять. 
    Создаем в каталоге ThreeTier файл ExampleOne.pm. Для того чтобы сервер знал когда и как его нужно выполнять редактируем файл httpd.conf, добавляем в него следующие строки:
<Location /exampleone>
   SetHandler perl-script
   PerlHandler ThreeTier::ExampleOne
</Location>
    Теперь при обращении по адресу http://your_server/exampleone Apache заставит mod_perl найти файл находящийся по относительному пути ThreeTier с именем ExampleOne и предопределенным типом .pm (стандартное расширение для модулей Perl). После этого он попытается исполнить находящуюся в этом модуле функцию handler. Отредактируем ExampleOne.pm следующим образом:
package ThreeTier::ExampleOne;
use strict;
use warnings;

sub handler
{
     my $r = shift;
     Apache->request( $r );
     $r->send_http_header('text/plain');
     if( $r->method ne 'POST')
     {
           print "Неверный метод вызова";
           return 0;
     }
      my $req = <STDIN>;
      print $req;
}

1;
    Разберем этот файл построчно. Определение модуля package ThreeTier::ExampleOne должно соответствовать имени файла и каталогов по относительному пути поиска, только разделители каталогов заменяются на ::, а в конце не учитывается расширение. use strict заставляет Perl более тщательно проверять файл при компиляции - например запрещает использование необъявленных предварительно переменных, use warnings - генерирует большее чем обычно количество сообщений при выполнении программы. Эти две директивы помогут с отладкой и предостерегут в некоторых случаях от грубых ошибок в программе. Далее следует объявление процедуры sub handler. По умолчанию это точка входа mod_perl, куда он передает управление после получение запроса к ExampleOne.pm. Дальше уже следует полностью подконтрольный нам текст программы.
    В качестве аргумента handler получает объект типа Apache::Request, вначале мы сохраняем его в локальной переменной $r, а в следующей строке приводим к необходимому типу. Отсылаем клиенту заголовок ответа с типом контента -  в данном случае - простой текст (можно применить text/html или еще что-нибудь - это может зависеть от файрволов и прокси-серверов, которые вы применяете, например некоторые файрволы проверяют документ на соответствие его содержания объявленному типу). Далее анализируем передан ли поступивший запрос по методу POST? Для этого служит свойство method объекта Apache::Request. Если метод не является POST - мы выходим из обработчика с признаком ошибки return 0 (нулевое значение возвращаемое функцией обычно служит для обозначения ошибки в mod_perl) и пересылаем клиенту строку с текстом предупреждения о неверном вызове. В случае если метод равен POST, то считываем текст присланного от клиента сообщения в локальную переменную $req (mod_perl переопределяет файл стандартного ввода на прием POST сообщений, а стандартный вывод - на ответ сервера), после чего отсылаем  ее обратно клиенту. На этом работа сервера завершается и он начинает ожидать следующий запрос.

Клиент

    Для создания клиента воспользуемся DELPHI, примеры написаны под версию 6, однако можно воспользоваться любой версией (от 4 до 7-й), которая поддерживает следующие  свободно распространяемые компонеты: RxLib для создания некоторых элементов интерфейса и временных таблиц в памяти и Indy (Internet direct) для поддержки http (https) протокола (обычно поставляется вместе с версиями 6 и 7). Установка этих компонентов обычно не вызывают никаких трудностей.
    Создаем новое приложение средствами DELPHI. Объявляем переменную HTTP типа TIdHTTP, при обработке создания главной формы вызываем конструктор для этого объекта, при закрытии - соответственно деструктор.
use  ....... IdHTTP;
......
private
    HTTP : TIdHTTP;
.......
procedure TfExample01.FormCreate(Sender: TObject);
begin
  HTTP := TIdHttp.Create(Self);
end;
........
procedure TfExample01.FormDestroy(Sender: TObject);
begin
  HTTP.Free;
end;
    Добавляем два мемо поля, называем их memPost и memReceive в первое будем записывать передаваемое сообщение, а во втором увидим возвращаемое значение (или текст ошибки). Создаем кнопку и по нажатию на нее пишем следующий обработчик:
procedure TfExample01.BitBtn1Click(Sender: TObject);
var
  tmpStream : TStringStream;
begin
  tmpStream := TStringStream.Create('');
  memReceive.Text := '';
  try
    HTTP.Post('http://your_server/exampleone', memPost.Lines, tmpStream);
    memReceive.Text := tmpStream.DataString;
  except
  on E : Exception do memReceive.Text := E.Message;
  end;
  tmpStream.Free;
end;
    HTTP.Post имеет три аргумента -
    В случае ошибки генерируется исключение - причем ошибка может возникнуть как из-за потери связи или отсутствия сервера в сети (если заменить your_server на your_server1 то появится ошибка Socket Error # 11001), так и от ошибки, возвращаемой сервером (например если мы заменим /exampleone на /exampleone1 то появится сообщение 404 Not found). Если все пройдет нормально, то в поле memReceive вы увидите копию текста memPost, если нет - то текст ошибки.

3-tier своими руками
Нормальная работа программы -
сообщение возвращено сервером
3-tier своими руками
Ошибка - сервер не найден
3-tier своими руками
Ошибка - не найден модуль или точка входа
О причинах смотрите в файлах лога.


    Если у вас произошла ошибка то посмотрите файлы /usr/local/apache/logs/access_log и /usr/local/apache/error_log. Они обычно помогают диагностировать ее причину. И не забывайте перезапускать сервер после каждого обновления текстов модулей и изменений в конфигурации!
Таким образом мы смогли передать сообщение на сервер и принять ответ от него. Конечно, практической пользы от этого примера немного - это своеобразный тест для диагностики работы сервера  и правильной установки всех компонентов.
    Вы можете скачать тексты клиента и сервера отсюда :

Пример №2

    Итак мы получили весьма бесполезную программу. Попробуем немного преобразовать ее, чтобы она могла очень много, буквально все что можно.

Сервер

    Для этого нужно совсем ничего, копируем файл ExampleOne.pm в ExampleTwo.pm :
root> cd /usr/local/apache/cgi-bin/ThreeTier
root> cp ./ExampleOne.pm ./ExampleTwo.pm
    Вносим в ExampleTwo.pm небольшие изменения (выделены цветом):
package ThreeTier::ExampleTwo;
use strict;
use warnings;

sub handler
{
     my $r = shift;
     Apache->request( $r );
     $r->send_http_header('text/plain');
     if( $r->method ne 'POST')
     {
           print "Неверный метод вызова";
           return 0;
     }
      my $req = <STDIN>;
      print eval( $req );
}

1;
    В первой строке меняем имя пакета на ThreeTier::ExampleTwo так как поменялось имя файла, а в последней строчке процедуры handler  выводим не строку запроса POST, а результат выполнения строки запроса как выражения Perl. Чтобы сервер смог увидеть этот модуль добавляем несколько строк в httpd.conf:
<Location /exampletwo>
   SetHandler perl-script
   PerlHandler ThreeTier::ExampleTwo
</Location>

Клиент

    Теперь делаем минимальные изменения в программе - клиенте:
procedure TfExample01.BitBtn1Click(Sender: TObject);
var
  tmpStream : TStringStream;
begin
  tmpStream := TStringStream.Create('');
  memReceive.Text := '';
  try
    HTTP.Post('http://your_server/exampletwo', memPost.Lines, tmpStream);
    memReceive.Text := tmpStream.DataString;
  except
  on E : Exception do memReceive.Text := E.Message;
  end;
  tmpStream.Free;
end;
    И все готово, осталось только перезапустить сервер командой /usr/local/apache/bin/apachectl restart. Но по причинам о которых чуть ниже - категорически и настоятельно рекомендуем перед этой командой вытащить из сервера все сетевые кабели  (а заодно и шнур питания). Но предположим что мы люди отважные до безрассудства и все же перезапустились. Теперь если в верхнем мемо поле написать 2+2 то после нажатия на кнопку Request мы получим ответ 4. Это уже какая никакая польза от программы - как никак удаленные вычисления! Вот некоторые примеры:
3-tier своими руками
Элементарно
3-tier своими руками
Чуть посложнее...
3-tier своими руками
Целая процедура !
3-tier своими руками
А так мы можем посмотреть что в каталогах
3-tier своими руками
А так почитать файл
3-tier своими руками
ОЙ...! (censured)

Продолжение следует. Если не хотите забыть скачать новый выпуск CompDocs, подпишитесь на наши рассылки, формы для подписки на главной странице.