Шаг 15 - Класс CGIApp

Давайте теперь подведем черту под тем, что было сделано в прошлых шагах. Все эти классы были предназначены для одного самого главного CGIApp.

Вот его описание:

class CGIApp{
public:
	CGIParam *Param;
	int Method;
	char *Query_String;
	long Content_Length;

	CGIApp();
	~CGIApp();
	int Init();
	CGIContent *FindParam(char *name);
};
Все просто как всегда, Param хранит полученные данные, Method содержит данные о методе передачи данных, который определяется следующими константами:
#define METHOD_GET 1
#define METHOD_POST 2
Переменные Query_String и Content_Length содержат значения полученные из соответствующих переменных окружения.

В конструкторе и деструкторе класса также все по старому:

CGIApp::CGIApp(){
	Param=new CGIParam();
	Query_String=NULL;
	Method=0;
	Content_Length=0;
};

CGIApp::~CGIApp(){
	delete Param;
	if (Query_String!=NULL) free(Query_String);
};

Особую "ценность" содержит в себе метод Init(). Эта процедура инициализирует все данные необходимые для работы приложения CGI, т.е. получает и декодирует все полученные от клиента данные. Сначала приведу текст этого метода:

int CGIApp::Init(){
	char *Buffer;
	char *Content_Len=NULL;
	char *Query_Str=NULL;
	char *ParamName=NULL;
	long Buffer_Len=0;
	long Buffer_Size=0;
	char Mode=0, ToMode=0;
	long i=0;
	char ch=0;
	CGIContent *temp=NULL;

	Buffer=getenv("REQUEST_METHOD");//получаем метод передачи данных
	if (Buffer==NULL) return 0;//если нет такой переменной то ошибка 
	
	Query_Str=getenv("QUERY_STRING");//строка в URL после "?"
	Content_Len=getenv("CONTENT_LENGTH");//длинна передаваемых данных

	if (strcmp(Buffer,"GET")==0){
		if (Query_Str!=NULL) Content_Length=strlen(Query_Str);
		else Content_Length=0;
		Method=METHOD_GET;
		
	} else {
		if (strcmp(Buffer,"POST")==0){
			Method=METHOD_POST;
			if (Content_Len!=NULL) Content_Length=atol(Content_Len);
			else Content_Len=0;
		} else goto proc_exit;
	};

	Buffer=NULL;
	Buffer_Size=10000;

	//инициализируем буффер для работы
	while (Buffer==NULL){
		Buffer=(char *)malloc(Buffer_Size);
		if (Buffer==NULL){
			if ((Buffer_Size-=1000)<0) return 0;
		};
	};

	//основной цикл
	while (i<Content_Length){
		//получаем следующий байт данных в 
		//зависимости от метода передачи
		switch (Method){
			case METHOD_GET:{
				ch=*(Query_Str+i);
				break;
			};
			case METHOD_POST:{
				fread(&ch,1,1,stdin);
				break;
			};
		};//switch;

		switch (Mode){
			case 0:{//режим накопления имени параметра
				if (ch!='='){
					if (ch=='+') ch=' '; else
					if (ch=='%'){
						ToMode=0;
						Mode=2;
						break;
					};
					*(Buffer+Buffer_Len)=ch;
					Buffer_Len++;
				} else {
					Mode=1;
					*(Buffer+Buffer_Len)=0;
					if (ParamName!=NULL) free(ParamName);
					ParamName=strdup(Buffer);
					Buffer_Len=0;
				};
				break;
			};//case 0;
			case 1:{//режим накопления содержимого атрибута
				if (ch!='&'){
					if (ch=='+') ch=' '; else
					if (ch=='%'){
						ToMode=1;
						Mode=2;
						break;
					};
					*(Buffer+Buffer_Len)=ch;
					if ((Buffer_Len+=1)>=Buffer_Size){
						*(Buffer+Buffer_Len)=0;
						if (ParamName!=NULL){
						        temp=Param->Add(ParamName,Buffer);
							free(ParamName);
							ParamName=NULL;
						} else {
							temp->Add(Buffer);
						};
						Buffer_Len=0;
					};
				} else {//если найден символ &, то добавляется параметр и значение
					*(Buffer+Buffer_Len)=0;
					if (ParamName!=NULL)
						temp=Param->Add(ParamName,Buffer);
					else
						temp->Add(Buffer);
					Buffer_Len=0;
					Mode=0;
				};
				break;
			};//case 1;
			case 2:{//режим преобразования из HEX первого символа после %
				*(Buffer+Buffer_Len)=(gethex(ch)<<4);
				Mode=3;
				break;
			};//case
			case 3:{//второго символа HEX
				*(Buffer+Buffer_Len)+=gethex(ch);
				Buffer_Len++;
				Mode=ToMode;
				break;
			};
		};//switch (Mode)
		i++;
	};

	//если осталось что-то после работы то добавляем
	if (Buffer_Len>0){
		*(Buffer+Buffer_Len)=0;
		if (ParamName!=NULL)
			temp=Param->Add(ParamName,Buffer);
		else
			temp->Add(Buffer);
	};
proc_exit:
	if ((Method==METHOD_GET)&&(Query_Str!=NULL))
	    Query_String=strdup(Query_Str);
	return 0;
};

Этот метод получает все переменные окружения необходимые для считывания приходящих данных. После чего инициализирует буффер и считывает их в зависимости от метода передачи.

В зависимости от считанного символа происходит смена режима и символ добавляется либо в имя параметра, либо в содержимое, либо декодируется.

Последний на этот момент необходимый метод поиска:

CGIContent *CGIApp::FindParam(char *name){
	CGIContent *temp=Param->Find(name);
	return temp;
};

Стоило бы заметить о некоторых особенностях работы класса на разных платформах. Изначально писал я его в среде Borland C++ 3.1 и в среде Windows все скрипты просто "летали". При отладке в Linux начались большие проблемы. После поиска ошибок, которых по идее не было, в течении получаса оказалось, что нельзя использовать процедуру free(*char), если указатель был получен с помощью getenv(). Т.е. я предполагаю в Линуксе процедура getenv() не копирует содержимое переменной окружения в отдельную строку, а возвращает адрес, в котором она содержится. В Досе таких ошибок не возникало, правда тут нельзя утверждать, что getenv() создает отдельную строку для этого, вероятно процедура free() просто не вызывает того аварийного завершения, которое получается в Линуксе.

Вобщем, что не система, то свои проблемы, да и что вообще там система... Компиляторы чего только стоят :-) Отладка CGI написанных на языке С++ всегда будет иметь некоторую зависимость от платформы или реализации компилятора, хотя считается, что все везде работает по одному алгоритму. Оказывается, что не все и не всегда.


Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Кузин Андрей - 04.05.2000