The Russian Electronic Developer Magazine | |
Русский электронный журнал разработчика | |
Речь пойдёт о создании распределённых приложений с использованием IBM Internet Connection Server (Lotus Go Server, Web Traffic Express) и языка Java. Точнее, о том, как написать на С серверную часть, а клиентскую на Java.
Начнём с более сложной, на мой взгляд, части - серверной. Я для себя остановился на ICAPI, программном интерфейсе ICS. Такой подход позволяет сосредоточится на разработке алгоритмов приложения, т.к. заботу о многопоточности, распределении памяти, вводе/выводе на себя берёт ICS. Кроме того, производительность будет гораздо выше, чем при использовании CGI.
В работе ICS выделены следующие шаги:
Программно все эти шаги реализованы в виде хуков в определённые места серверного кода. Каждому хуку соответствует директива из файла конфигурации (httpd.cnf). Хук - это функци на С (С++) скомпилированная, экспортированная и помещённая в dll-ку. На каждый шаг может быть "навешано" несколько хуков. Первым управление получит тот, который первым встретится в директивах конфигурации. А вопрос получит ли следующий хук управление? - решается кодом возврата (мне правда кажется, что не на всех шагах), если код HTTP_OK, значит функция-хук успешно обработала запрос на своем шаге и никаких дополнительных действий не требуется, другие хуки (даже стандартный обработчик) вызываться не будут. Если же код HTTP_NOACTION, то следующий хук получит управление по цепочке. Если же в цепочке хуков ни один не вернул код HTTP_OK, будет выполнена стандартная процедура обработки. Более конкретно эти вопросы надо рассматривать на каждом отдельном шаге. Кстати, коды возврата функций-хуков имеют префикс "HTTP_".
При написании своих обработчиков, в код необходимо
включать файл
Я для работы использую IBM VA C++ 3.0 for OS/2, поэтому все
примеры будут для него.
Итак, любая функция обработчик должна выглядеть примерно так:
Вторая вызывается столько раз, сколько нужно чтобы передать
все данные в ответ на запрос (понятно, что весь документ может
не поместиться в буфер сервера). И параметров у этой функции
больше, ей передается указатель на буфер с данными и указатель
на переменную содержащую размер этих данных. Хочу предостеречь
вас от прямой модификации значений этих переменных, так как вы
рискуете налететь на "Memory protection violation".
Скорее всего у вас уже возникли вопросы, да действительно,
информации передаваемой через параметры функций во многих
случаях недостаточно. Недостающее можно в ICAPI получить через
переменные сервера, назовем из именованными переменными (или
переменными окружения, в оригинале) чтобы отличать от переменных
языка C. Набор именованных переменныех в ICAPI и CGI для ICS
совпадают. Т.е. вы можете получить значение такой переменной CGI
как "REMOTE_ADDR" (IP адрес клиента). Здесь надо обратить
внимание на то, что не на всех шагах обработки запроса доступны
все переменные. Так, например, "PATH_TRANSLATED" на шаге PreExit
еще не определена, просто потому, что сервер еще не выполнил
необходимых для этого действий и отдал управление вашей функции.
Описания, конкретно, какие переменные, когда действительны я не
нашел, но мне пока помогал здравый смысл.
Для работы с переменными, а также для выполнения других
полезных действий интерфейс ICAPI предоставляет в ваше
распоряжение набор стандартных функций и макросов. Я приведу
перевод части документации связанной с этим вопросом.
Еще одно замечание. Постарайтесь избегать статических и глобальных
переменных (помните, сервер - существо многопоточное, нужны
блокировки), обработчики должны быть реентерабельными. И
последнее - не злоупотребляйте стеком, большой стек приводит к
большим трапам :-)
В качестве примера рассмотрим часть кода
RHTTP-перекодировщика "на-лету". Код приведен не полностью
(интересующиеся смогут забрать текущий вариант со всеми потрохами
на ftp://cbs-edu.chel.su/pub/OS2/RHTTP, когда я доделаю программу).
Необходимо :
Для сборки DLL-ки нужен DEF-файл. В моем случае я
воспользовался шаблоном IBM VA C++ и он выглядит так :
В заключение хочу привести полностью код функции получающей
значение именованной переменной.
Это было описание серверной части, так сказать stand-alone. Про
взаимодействие сервера и java-клиента немного позже.
#include ... // нужные вам H-файлы
#include <HTAPI.H> // это файл из поставки ICS
void HTTPD_LINKAGE Some_MyFunc (
unsigned char *h, long *rcode )
{
...
*rcode = HTTP_OK; // код возврата обязателен
}
Конкретный набор параметров обработчика зависит от шага
(см.выше) для которого вы его пишите. В принципе, нужные шаблоны
описаны в документации на ICS, но я думаю не будет лишним
привести его здесь.
void HTTPD_LINKAGE ServerInit (
unsigned char *handle, unsigned long *maj_ver,
unsigned long *min_ver, long *rcode );
Обратите внимание на параметр handle, его назначение
примерно такое же как и у файлового handle, но мне не удалось
найти указаний на то, что этот handle связан с конкретным
сеансом или хотя бы запросом (т.е. он уникален для каждого
запроса и не меняет своего значения на разных шагах обработки),
поэтому я побоялся использовать его для идентификации.
void HTTPD_LINKAGE PreExit (
unsigned char *handle, long *rcode );
void HTTPD_LINKAGE Authentication (
unsigned char *handle, long *rcode );
void HTTPD_LINKAGE NameTrans (
unsigned char *handle, long *rcode );
void HTTPD_LINKAGE Authorization (
unsigned char *handle, long *rcode );
void HTTPD_LINKAGE ObjectType (
unsigned char *handle, long *rcode );
void HTTPD_LINKAGE Service (
unsigned char *handle, long *rcode );
А для шага DataFilter можно задать три функции. Первая:
void HTTPD_LINKAGE Open (
unsigned char *handle, long *rcode );
Вызывается один раз перед началом отправки данных сервером
клиенту (причем, что не описано в документации, вызавается до
шага PreExit, т.е. сначала идет Open, и только затем PreExit).
void HTTPD_LINKAGE Write (
unsigned char *handle, unsigned char *data,
unsigned long *datalength, long *rcode );
И третья функция предназначена для "закрытия" выходного
потока.
void HTTPD_LINKAGE Close (
unsigned char *handle, long *rcode );
Далее...
void HTTPD_LINKAGE Log (
unsigned char *handle, long *rcode );
void HTTPD_LINKAGE Error (
unsigned char *handle, long *rcode );
void HTTPD_LINKAGE PostExit (
unsigned char *handle, long *rcode );
void HTTPD_LINKAGE ServerTerm (
unsigned char *handle, long *rcode );
Имена функций даны условно, для иллюстрации к какому шагу
относится тот или иной шаблон. Вы будете давать свои имена.
Стандартные функции также возвращают коды ошибок, эти коды
следует отличать от тех, что должны возвращать ваши обработчики.
Следите за тем, чтобы код возврата стандартной функции не попал
выше (будете долго искать ошибку). Коды приведены в
void HTTPD_authenticate (
unsigned char *handle,
long *rcode );
void HTTPD_extract (
unsigned char *handle,
unsigned char *name, // именованая переменная
unsigned long *name_length,// длина имени переменной
unsigned char *value, // буфер под значение
unsigned long *val_length,// размер буфера под значение
long *rcode );
Возникает вопрос, как определить размер буфера, здесь используется
следующий прием :
{
unsigned char Buf [2];
unsigned long BufSize;
unsigned char *Name;
unsigned long NameSize;
unsigned char *BigBuf;
long RetCode;
Name = "REMOTE_ADDR";
NameSize = strlen(Name);
BufSize = sizeof(Buf);
HTTPD_extract( handle,Name,&NameSize,Buf,&BufSize,&RetCode);
if ( RetCode == HTTPD_BUFFER_TOO_SMALL )
{
if ( (BigBuf=malloc(BufSize+1)) != NULL )
HTTPD_extract(
handle,Name,&NameSize,Buf,&BufSize,&RetCode);
}
То есть, если после первого вызова получили код ошибки -
"буфер мал", то в переменную BufSize ICS помещает нужный
размер буфера, после чего можно смело его динамически выделять и
повторять попытку извлечения значения именованной переменной.
void HTTPD_set (
unsigned char *handle,
unsigned char *name, // именованая переменная
unsigned long *name_length,// длина имени переменной
unsigned char *value, // буфер значения
unsigned long *val_length,// размер буфера значение
long *rcode );
void HTTPD_file (
unsigned char *handle,
unsigned char *Filename,
unsigned long *Filename_length,
long *rcode );
void HTTPD_exec (
unsigned char *handle,
unsigned char *Progname,
unsigned long *Progname_length,
long *rcode );
void HTTPD_read (
unsigned char *handle,
unsigned char *value,
unsigned long *value_length,
long *rcode );
От себя добавлю, что с длиной буфера дело обстоит также как
и в функции HTTPD_extract.
void HTTPD_write (
unsigned char *handle,
unsigned char *data,
unsigned long *data_length,
long *rcode );
void HTTPD_log_error (
unsigned char *handle,
unsigned char *text,
unsigned long *text_length,
long *rcode );
void HTTPD_log_trace (
unsigned char *handle,
unsigned char *text,
unsigned long *text_length,
long *rcode );
void HTTPD_restart ( long *rcode );
Функцию для шага PreExit я опускаю, в ней нет ничего
примечательного.
void HTTPD_LINKAGE RHTTP_Init (unsigned char *handle,
unsigned long *major_version,
unsigned long *minor_version,
long *rcode )
{
char Buf [256];
unsigned long length;
unsigned char *serverROOT;
...
/*
Теперь необходимо определить каталог, в который помещена программа
*/
if ( (serverROOT=RHTTP_GetCGIvar(handle,SERVER_ROOT)) != NULL )
{ // я сделал свою функцию для
// получения значений именованых
// переменных.
...
}
...
*rcode = HTTP_OK; // нужно вернуть именно HTTP_OK,
} // иначе это будет ошибкой инициализации
// сервера с вытекающими последствиями.
void HTTPD_LINKAGE RHTTP_Open ( unsigned char *handle, long *rcode )
{
unsigned char *remoteADDR = NULL;
unsigned char *querySTRING = NULL;
unsigned char *userAGENT = NULL;
unsigned char CODEPAGE [_MAX_CPAGENAME];
RHTTP_User volatile *UserPtr = NULL;
short volatile Codepage;
/*
Необходимо выделить адрес запрашивающего и тип его броузера.
Это переменные REMOTE_ADDR и HTTP_USER_AGENT
*/
if( (remoteADDR=RHTTP_GetCGIvar(handle,REMOTE_ADDR)) != NULL &&
(userAGENT=RHTTP_GetCGIvar(handle,HTTP_USER_AGENT)) != NULL )
{ // нормализуем имя агента пользователя и поищем
if ( (UserPtr=RHTTP_FindUser(remoteADDR,userAGENT)) == NULL )
{ // если не найдем, то добавим в таблицу, рано
if ( (UserPtr=RHTTP_AddUser(remoteADDR,userAGENT)) == NULL )
{ // или поздно он к нам прийдет
RHTTP_LogError(handle,Err_NotEnoughMemory);
}
}
else
{ // если нашли, то сбросим исходную
DosEnterCritSec(); // кодировку для пользователя
UserPtr->SourceCP = SourceCodepage;
DosExitCritSec();
} // проверим, есть ли запрос на
// смену кодовой страницы, для этого выделим
if ( UserPtr != NULL )
{
if ( (querySTRING=RHTTP_GetCGIvar(handle,QUERY_STRING)) != NULL )
{ // пользовательский запрос, затем поищем
CODEPAGE [0] = '\0';// в нем переменную на смену кодовой стр.
RHTTP_VarValue ( querySTRING, CGI_Codepage,
CODEPAGE, sizeof(CODEPAGE)-1 );
if ( CODEPAGE [0] )
{
Codepage = RHTTP_GetCodepage(CODEPAGE);
DosEnterCritSec();
UserPtr->Codepage = Codepage;
DosExitCritSec();
}
}
RHTTP_SetContentEncoding ( handle, UserPtr->Codepage );
}
}
if ( remoteADDR != NULL )
free ( remoteADDR );
if ( querySTRING != NULL )
free ( querySTRING );
if ( userAGENT != NULL )
free ( userAGENT );
*rcode = HTTP_OK;
}
Обратите внимание, эта функция должна вернуть HTTP_OK, иначе
оставшиеся две ваших функции будут проигнорированы, т.е.
считается что у вас произошла ошибка при открытии и смысла
вызывать остальные функции нет.
void HTTPD_LINKAGE RHTTP_Close ( unsigned char *handle, long *rcode )
{
*rcode = HTTP_NOACTION; // по закрытию соединения ничего не делаем
} // о чем и сообщаем соответствующим кодом.
void HTTPD_LINKAGE RHTTP_Write ( unsigned char *handle,
unsigned char *data, unsigned long *datalength,
long *rcode )
{
RHTTP_User volatile *UserPtr = NULL;
unsigned char *remoteADDR = NULL;
unsigned char *userAGENT = NULL;
unsigned char *pathTRANSLATED = NULL;
/*
Необходимо выделить адрес запрашивающего и тип его броузера.
Это переменные REMOTE_ADDR и HTTP_USER_AGENT, точно также как
и на шаге Open.
*/
...
*rcode = HTTP_OK;
if( (remoteADDR=RHTTP_GetCGIvar(handle,REMOTE_ADDR)) != NULL &&
(userAGENT=RHTTP_GetCGIvar(handle,HTTP_USER_AGENT)) != NULL )
{ // нормализуем имя агента пользователя и поищем
UserPtr=RHTTP_FindUser(remoteADDR,userAGENT);
}
/*
Выделим запрашиваемый URL.
*/
if ( (pathTRANSLATED=RHTTP_GetCGIvar(handle,PATH_TRANSLATED)) == NULL &&
(pathTRANSLATED=RHTTP_GetCGIvar(handle,DOCUMENT_URL)) == NULL )
{ // если были ошибки при выделении, то
HTTPD_write ( handle, data, datalength, rcode );
*rcode = HTTP_OK; // просто отдадим данные
}
else
{
if ( RHTTP_GetDataType(pathTRANSLATED) == _CONTENT_TEXT )
{ // проверим тип данных, обрабатывать
// будем только текстовые данные
if ( !RHTTP_CheckSite(pathTRANSLATED) || UserPtr == NULL )
{ // проверим, надо ли перекодировать данный
// источник или нет, если не надо, то
HTTPD_write ( handle, data, datalength, rcode );
*rcode = HTTP_OK; // просто отдадим данные
}
else
{
if ( UserPtr->SourceCP != UserPtr->Codepage )
RHTTP_Translate ( data, *datalength,
To866_Codepages[UserPtr->SourceCP],
From866_Codepages[UserPtr->Codepage]);
RHTTP_write ( handle, data, datalength, rcode );
*rcode = HTTP_CREATED;
}
}
else // см. выше
{ // если тип данных двоичный, просто отдадим
HTTPD_write ( handle, data, datalength, rcode );
*rcode = HTTP_OK;
}
}
if ( remoteADDR != NULL )
free ( remoteADDR );
if ( userAGENT != NULL )
free ( userAGENT );
if ( pathTRANSLATED != NULL )
free ( pathTRANSLATED );
*rcode = HTTP_OK;
}
;========================================================================
; rhttp.def - Intermediate dynamic linking library definition file
;========================================================================
LIBRARY rhttp INITINSTANCE TERMINSTANCE
PROTMODE
DATA MULTIPLE NONSHARED READWRITE LOADONCALL
CODE LOADONCALL
EXPORTS
RHTTP_Open
RHTTP_Close
RHTTP_Write
RHTTP_Init
RHTTP_PreExit
Для подключения полученных функций в файл httpd.cnf
добавлены следующие строки (в версии WTE 1.1.2 следует имя
DLL писать полностью):
ServerInit E:\WWW\RHTTP\rhttp.dll:RHTTP_Init
DataFilter E:\WWW\RHTTP\rhttp.dll:RHTTP_Open:RHTTP_Write:RHTTP_Close
PreExit E:\WWW\RHTTP\rhttp.dll:RHTTP_PreExit
Несколько слов об отладке. Для отладки удобно пользоваться
опцией сервера "Trace On". В этом случае в окно "TraceLog"
сервера выводится достаточно подробная и разнообразная
инфорамция о том что, в какой последовательности и как
вызывается.
#define _MAX_BUF 2
unsigned char *RHTTP_GetCGIvar ( unsigned char *handle, CGI_Var Var )
{
unsigned char *Buf;
unsigned long maxsize = _MAX_BUF;
long rcode = HTTPD_SUCCESS;
if ( (Buf=malloc(maxsize)) != NULL ) // размещаем буфер
{ // если все ОК, выделяем переменную
HTTPD_extract(handle,Var.name,&Var.size,Buf,&maxsize,&rcode);
if ( rcode == HTTPD_BUFFER_TOO_SMALL )
{ // если ошибка размера буфера
free ( Buf ); // освободим занимаемую память
if ( (Buf=malloc(maxsize+1)) != NULL )
{ // выделим новый, побольше
HTTPD_extract(handle,Var.name,&Var.size,Buf,&maxsize,&rcode);
Buf [maxsize] = '\0'; // и снова выделяем переменную
}
else
{
RHTTP_LogError(handle,Err_NotEnoughMemory);
}
}
}
else
{
RHTTP_LogError(handle,Err_NotEnoughMemory);
}
if ( rcode != HTTPD_SUCCESS && rcode != HTTPD_PARAMETER_ERROR )
{
RHTTP_LogErrorCode(handle,rcode);
}
return ( Buf );
}
Андрей Породько
Программирование ICAPI для ICS. Часть 2
Интересные ссылки:
Комментариев к странице: 0 | Добавить комментарий
Редактор: Дмитрий Бан
Оформление: Евгений Кулешов
(C) Russian Underground/2