ModCBroker предоставляет простой но мощный механизм разработки WEB приложений и интеграции CORBA приложений в WEB. Работа ModCBroker, как и любого другого сервера приложений, заключается в трансляции поступающих http запросов в CORBA запросы, и предоставлении программисту программного интерфейса (API) для чтения параметров и вывода результата запросов в WWW браузере пользователя.
Т. е. в общем виде картина выглядит так:

Предоставляемое API полностью описанно в разделе IDL интерфейсы 7, с точки зрения "птичьего полета" процесс построения WWW приложения с помощью cbroker можно описать следующим образом:
Это руководство описывает интерфейсы программирования, компиляция, установка и администрирование описанно в [9], аспекты использования mod_cbroker с точки зрения архитектуры программных комплексов описанны в [2].
Передается в структуре RequestInfo
struct RequestInfo
{ ClientInfo client_info; ServerInfo server_info; ParameterSequence parameters; ParameterSequence cookies; BinaryParameterSequence binary_parameters; string method; }; |
Как мы видим, в ней содержится:
struct ClientInfo
{ string ip; string hostname; string user_name; }; |
где
struct ServerInfo
{ string hostname; short port; }; |
struct Parameter
{ string name; string value; }; typedef sequence<Parameter> ParameterSequence; |
Заметим, что GET и PUT параметры с точки зрения CORBA сервлета неразличимы.
struct BinaryParameter
{ string name; CORBA::OctetSeq value; }; typedef sequence<BinaryParameter> BinaryParameterSequence; |
С их помощью можно передавать двоичные данные, организовав, например, загрузку файлов из html формы.
Отдельного рассмотрения заслуживает случай передачи файла на WWW сервер с помошью WWW формы.
В этом случае в binary_parameters.name записывается имя тега INUT элемента формы, при помощи которой был загружен файл.
Например, если файл загружен при помощи
<INPUT TYPE=FILE NAME="pics">
|
то имя тега - "pics". Сам файл передается в поле binary_parameters.value, а полное имя файла можно получить из обычного параметра запроса с именем name+"_filename". В нашем случае это был бы параметр "pics_filename".
Этот обьект предоставляет API для вывода в клиентский браузер, фактически экспортируя для этой цели API apache [6].
Вот, к примеру, фрагмент кода, выводящий HTML страницу "Hello, World":
httpStream.set_content_type("text/html");
httpStream.send_http_header(); httpStream.puts("<HTML><HEAD><TITLE>Hello, World</TITLE><HEAD>" "<BODY><CENTER>Hello, World</CENTER></BODY></HTML>"); |
Рассмотрим этот интерфейс подробнее:
interface HTTPStream
{ void set_content_type(in string content_type); void set_http_header(in string name, in string value); void set_cookie(in string name, in string value, in unsigned long expire_time); void send_http_header(); void send_http_header_ex(in ReplyHeaderInfo header_info); string get_http_header(string name); void puts(in string str) raises(StreamClosedException); unsigned long send_buffer(in CORBA::OctetSeq buffer) raises(StreamClosedException); void flush() raises(StreamClosedException); }; |
struct ReplyHeadeInfo
{ string content_type; ParameterSequence fields; ParameterSequencr cookies; unsigned long cookie_expire_time; }; |
Как видим здесь просто собраны тип контента, значения заголовков и cookie-s. Единственное отличие - всем посланным таким способоб cookies присваивается одинаковое время жизни, заданное cookie_expire_time (в секундах).
Хорошо, теперь мы знаем как пользоваться API вывода, а где его применять? Для этой цели разработчику предоставляется интерфейс Handler.
interface RequestHandler
{ void handle(in RequestInfo request_info, in HTTPStream stream) raises(ExternalException, StreamClosedException, Redirect); void destroy(); }; |
Разработчик должен его реализовать как CORBA объект, и поместить обработку запроса в методе handle.
Функция может бросить исключение "ExternalException", если ей необходимо зафикисировать сообщение об ошибке в log файле Apache.
exception ExternalException
{ short http_code; string reason; }; |
Как видим из определения, программист может задать HTTP код возврата и строку, описывающую ситуацию, которая будет напечатана на экране пользователя и в log файле WWW сервера.
Напомним, что исключение StreamClosedException вызывается в API HTTPStream. Оно помещено в декларацию интерфейса для того, что-бы если программист его не обработал при возникновении, то WWW сервер сам произвел бы все необходимые действия.
Исключение Redirect используется для перенаправления запроса по другому URL, его удобно использовать для интеграции ModCbroker и друших скрипт-приложений.
exception Redirect
{ string url; // url to redirect. ParameterSequence parameters; // parameters. }; |
Реакция Web сервера на такое исключение - генерация GET запроса по указанному URL и с параметрами, указанными как аргумент.
Вот, к примеру, Handler для вывода HelloWorld:
class HelloWorldHandler: POA_HTTP::RequestHandler
{ PortableServer::POA_var poa_; public: HelloWorldHandler(PortableServer::POA_ptr poa) :poa_(PortableServer::_duplicate(poa) {} void handle(const RequestInfo& request_info, HTTPStream_ptr httpStream) { httpStream.set_content_type("text/html"); httpStream.send_http_header(); httpStream.puts("<HTML><HEAD><TITLE>Hello, World</TITLE><HEAD>" "<BODY><CENTER>Hello, World</CENTER></BODY></HTML>"); } void destroy() { PortableServer::ObjectId_var oid = poa_->servant_to_id(this); poa_->deactivate_object(oid); _remove_ref(); } }; |
(Тут для простоты предположенно, что мы используем полностью совместимую с CORBA-2.3 ORB)
Теперь рассмотрим интерфейс Servlet (Сервлет). Этот интерфейс тоже должен быть реализован разработчиком конечного приложения; Главные задачи, решаемые этим интерфейсом, следующие:
Смотрим на интерфейс:
interface Servlet
{ RequestHandler create_handler(in string handlerName, in ClientInfo client_info, in ServerInfo server_info, in string passwd) raises(NoSuchHandlerException, RequireAuthInfo, AccessDenied, ExternalException, Redirect); }; |
Метод createHandler совмещает в себе авторизацию и создание обработчика для запроса; впоследствии cbroker вызывет для него метод handle, а после этого удалит обработчик. Если username или passwd не проходит авторизации, то возбуждается исключение RequireAuthInfo. Если авторизация прошла неуспешно по другим причинам, то возбуждается исключение AccessDenied.
В качестве примера, напишем сервлет на C++, который запускает наш HelloWorld, в ответ на запрос Обработчика "helllo" только для пользователя с именем "admin" и паролем "qq888ikd930" с хоста 200.200.220.10.
class HelloWorldServlet: public POA_HTTP::Servlet
{ PortableServer::POA_var poa_; public: HelloWorldServlet(PortableServer::POA_ptr poa) :poa_(PortableServer::_duplicate(poa)) {} HTTP::RequestHandler_ptr create_handler(in string handlerName const HTTP::ClientInfo& clientInfo, const HTTP::ServerInfo& serverInfo, const char* passwd) { if (strcmp(handlerName,"hello")!=0) throw NoSuchHandlerException(); // разрешаем только admin-у с хоста 200.200.220.10 и с паролем qq888ikd930 if (strcmp(clientInfo.user_name,"admin")!=0) throw RequireAuthInfo(); if (strcmp(clientInfo.ip,"200.200.220.10")!=0) throw AccessDenied(); if (strcmp(passwd,"qq888ikd930")!=0) throw RequireAuthInfo(); if (strcmp(handlerName,"hello")!=0) throw NoSuchHandlerException(); HelloWorldHandler* handler_impl = new HelloWorldHandler(poa_); HTTP::RequestHandler_var retval = handler_impl->_this(); return retval._retn(); } }; |
Или напишем сервлет, который не генерирует обработчиков, но перенаправляет пользователя на свою персонализированную страницу:
class CheckServlet: public POA_HTTP::Servlet
{ UAKGQuery2::QueryManager_var queryManager_; public: CheckServlet(UAKGQuery2::QueryManager_ptr queryManager) :queryManager(UAKGQuery2::QueryManager::_duplicate(queryManager)) {} HTTP::RequestHandler_ptr createHandler(in string handlerName, const HTTP::ClientInfo& clientInfo, const HTTP::ServerInfo& serverInfo, const char* passwd) { if (!strcmp(clientInfo.username,"unknown")) { throw RequireAuthInfo(); } UAKQUery2::RecordSet_var params=UAKGQuery2::RecordSet::create(2,1); params.setColumnName(0,"login"); params.setColumnName(1,"passwd"); params.setString(0,0,clientInfo.username.in()); params.setString(0,1,passwd); UAKQUery2::RecordSet_var rs = queryManager_->evaluate( "select home_url from users where " "login = :login " " and " "password = :password " ); if (rs.getNRows()==0) { throw AccessDenied(); } String_var home_url=rs.getAsString(0,0); Redirect redirect; redirect.url=CORBA::string_dup(home_url.in()); throw Redirect(); } }; |
Последний вопрос: допустим мы все реализовали, как cbroker найдет сервлет и имя обработчика по URL?
Ответ: С помощью CORBA Name Service [3]. Т. е. если path часть URL запроса выглядит следующим образом:

Соответственно, разработчик должен при инициализации сервлета связать его с именем HTTPServ/ServletName. Примеры можно найти в директории demo дистрибутива ModCBroker.
Наконец, напомним что программма, использующая cbroker API должна быть скомпилированна с клиентской библиотекой (clcbroker для C++, или clcbroker.jar) для Java, и что процесс настройки www сервера для работы с ModCBroker описан в [9]
В ряде случаев нам хочется отделить генерирацию контента от выбора содержания: скажем некоторые части web приложения (критичные по времени либо связанны с back-end функциональностью) должны быть оформлены как CORBA сервлеты, но с другой стороны сам Web интерфейс контролируется дизайнером сайта.
Такое разделение контента и содержания может быть реализовано как со стороны сервлета (CASL предоставляет похожую функциональность) так и со стороны Apache. На практике оказалось, что последнее также достаточно удобно.
В cbroker встроенна поддержка такой возможности в виде фильтра и в виде API взаимодействия со скрипт языками.
Использование фильтров позволяет встраивать вызовы сервлетов ModCbroker в Web страницы: выражение вида
<?cbroker ServletName HandlerName p1="v1" ... p2="vN" ?>
|
заменяется на результат вызова обработчика handlerName сервлета ServletName с параметрами pi,vi. В структуре RequestInfo метод устанавливается в строку ’CBROKER-INCLUDE’
Для этого достаточно просто настроить обработку текста HTML фильтром Cbroker.
К примеру, пусть у нас есть сервлет, выводящий в окно браузера список внутренних телефонов работников предприятия, который называется, скажем: NSI/emp.
В традиционной технологии программирования, при каждой смене дизайна сайта надо обращаться к программисту, чтобы он модифицировал соответствующий сервлет. С появлением ModCbroker-3.1.0 эта необходимость отпала, теперь мы:
<HTML>
<HEAD> <TITLE> Список работников </TITLE> </HEAD> <BODY> <img src="images/OurGreatImageOnTop.gif" /><br></br> <?cbroker NSI Emp ?> <img src="images/OurGreatImageOnBottom.gif" /><br></br> </BODY> |
AddOutputFilterByType CBROKER cbhtml
|
Все, теперь наш программист может заниматься только семантикой, а дизайнер - дизайном.
Теперь, допустим у нас еще есть обработчик, который ищет человека по фамилии и выводит подробную информацию. Скажем, NSI/emp_search. К примеру, на домашней странице пользователя scott1tiger есть следующий блок:
<?cbroker NSI emp_search name="jon" family="scott" ?>
|
Это хорошо, если у нас страница статичная, а если мы хотим чтобы имя и фамилия человека, которого надо искать, передавадась в параметрах запроса?
Для этого есть специальный параметр: passRequestParameters.
<?cbroker NSI emp_search passRequestParameters="1" ?>
|
Можно одновременно передавать параметры из текста и из запроса.
<?cbroker NSI emp_search family="scott" passRequestParameters="1" ?>
|
В этом случае, если имя какого-то параметра в запросе совпадает с именем параметра в строке вызова то приоритет отдается второму. Почему так? Чтобы мы могли писать в тексте html тексты SQL выражений и не бояться их подмены.
Кстати, для параметров запроса существует несколько разных способов приема:
Фильтры можно комбинировать друг с другом. Например, использовать mod_include для генерации заголовков и окончаний страниц:
AddOutputFilter CBROKER;INCLUDE .scbhtml
|
Тогда наш файл будет сначала обрабатываться с помощью CBROKER, а потом резудьтат этого преобразования будет обработан фильтром INCLUDE.
<HTML>
<HEAD> <TITLE> Список работников </TITLE> </HEAD> <BODY> <--#include virtual "/topOfOurPage/" --><br></br> <?cbroker NSI Emp ?> <--#include virtual "/bottomOfOurPage/" --><br></br> </BODY> |
Точно так же можно комбинировать использование CBROKER и XSLT или PHP. Впрочем, для PHP лучше использовать специальный интерфейс работы со скриптами так как комбинация простой подстановки текста и языка программирования выглядит не очень естественно.
При выводе html страниц с включениями тегов cbroker Вам необходимо отдельно реализовать авторизацию: ведь сервер должен знать имя пользователя до начала вывода страницы.
Для этой цели предназнаячена директива CbrokerAddAuthByLocation, которая позволяет задать авторизирующий сервлет для определенной директории. К примеру:
CbrokerAddAuthByLocation /cbf /Billing /auth
|
Это означает, что доступ ко всем файлам расположенным в /cbf (и в подкаталогах) регулируется с помощью сервлета Billing, вызывая там обработчик auth. Протокол взаимодействия очены простой - сервлет при запросе авторизации ведет себя точно так-же, как и при любом другом запросе - возвращает значение либо генерирует исключительную ситуацию. Единственное отличие - нас не интересует возвращаемое значение, т. е. мы абсолютно спокойно можем возвратить NIL. К примеру, посмотрим на следуйщий блок кода:
class HelloWorldServlet: public POA_HTTP::Servlet
{ PortableServer::POA_var poa_; public: HelloWorldServlet(PortableServer::POA_ptr poa) :poa_(PortableServer::_duplicate(poa)) {} HTTP::RequestHandler_ptr create_handler(in string handlerName const HTTP::ClientInfo& clientInfo, const HTTP::ServerInfo& serverInfo, const char* passwd) { if (strcmp(handlerName,"auth")==0) { // разрешаем только admin-у с хоста 200.200.220.10 и с паролем qq888ikd930 if (strcmp(clientInfo.user_name,"admin")!=0) throw RequireAuthInfo(); if (strcmp(clientInfo.ip,"200.200.220.10")!=0) throw AccessDenied(); if (strcmp(passwd,"qq888ikd930")!=0) throw RequireAuthInfo(); return HTTP::RequestHandler::_nil(); } } }; |
Естественно, в конфигурации Apache также надо задать способ авторизации для соответствующего местоположения. Перед использованием директивы CbrokerAddAuthByLocation, пожалуйста, прочитайте соответствующий раздел в руководстве администратора.
Вы можете вызывать сервлеты ModCbroker из скриптов websh [7]. Синтаксис вызова:
cbroker servletName handlerName {param-list}
|
В параметрах должны быть последовательности пар имя/значение.
Например:
[cbroker Billing AccountState { username $username userid $userid }]
|
Для этого в конфигурации websh скрипта Вам нужно загрузить библиотеку webshcbroker.
Пример вызова нашего Hello, World из первой главы:
#
# Demo for interactuion of ModCbroker with Tcl # web::initializer{ # load cbroker interaction library load /usr/local/lib/libweshcbroker83.so.1 } web::command default { web::put { "<HTML><HEAD> Hello World demo </HEAD>" "<BODY>" "<center>" " [cbroker Hello World {} ] " "</center>" "</BODY>" "</HTML>" } } web::dispatch |
Этот раздел предназначен для авторов (и соавторов) других модулей Apache. Если вы хотите вызвать сервлет ModCbroker из другого модуля (например, для доступа к CORBA объектам из D