Click here to Skip to main content
15,892,839 members
Articles / Programming Languages / C++

Power of C++ - Developing a Portable HTTP Server with Python Interpreter using Boost, TinyXML

Rate me:
Please Sign up or sign in to vote.
4.83/5 (14 votes)
1 Jun 2008CPOL6 min read 61.6K   1.5K   63  
This article describes portable networking library (ahttp) and small HTTP server - result of modern C++ programming approaches investigation
/*
This file is part of [ahttp] library. 

Author: Artem Kustikov (kustikoff[at]tut.by)
version: 0.1

This code is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this code.

Permission is granted to anyone to use this code for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:

1. The origin of this code must not be misrepresented; you must
not claim that you wrote the original code. If you use this
code in a product, an acknowledgment in the product documentation
would be appreciated but is not required.

2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original code.

3. This notice may not be removed or altered from any source
distribution.
*/

#include "aconnect/boost_format_safe.hpp"

#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>

#include <assert.h>

#include "tinyxml/tinyxml.h"

#include "aconnect/util.hpp"
#include "ahttp/http_support.hpp"
#include "ahttp/http_server_settings.hpp"

#if defined(__GNUC__)
#	include <dlfcn.h>
#endif

namespace algo = boost::algorithm;
namespace fs = boost::filesystem;

namespace ahttp
{

	DirectorySettings::DirectorySettings () : 
		browsingEnabled (-1), 
		isLinkedDirectory(false),
		charset ()
	{

	}

	HttpServerSettings::HttpServerSettings() :
		port_(-1), 
		commandPort_ (-1),
		logLevel_ (aconnect::Log::Debug), 				   
		maxLogFileSize_ (aconnect::Log::MaxFileSize), 
		enableKeepAlive_ (defaults::EnableKeepAlive),
		keepAliveTimeout_ (defaults::KeepAliveTimeout),
		commandSocketTimeout_ (defaults::CommandSocketTimeout),
		responseBufferSize_ (defaults::ResponseBufferSize),
		maxChunkSize_ (defaults::MaxChunkSize),
		logger_ (NULL),
		serverVersion_ (defaults::ServerVersion),
		firstLoad_ (true),
		directoryConfigFile_ (defaults::DirectoryConfigFile)
	{
		settings_.socketReadTimeout = defaults::ServerSocketTimeout;
		settings_.socketWriteTimeout = defaults::ServerSocketTimeout;
	}

	HttpServerSettings::~HttpServerSettings()
	{
	}

	aconnect::string HttpServerSettings::getMimeType (aconnect::string_constref ext) const 
	{
		using namespace aconnect;
		
		str2str_map::const_iterator it = mimeTypes_.find(ext);
		if (it != mimeTypes_.end())
			return it->second;

		return detail::ContentTypeOctetStream;			
	}


	void HttpServerSettings::load (aconnect::string_constptr docPath) throw (settings_load_error)
	{
		if ( !docPath || docPath[0] == '\0' ) 
			throw settings_load_error ("Empty settings file path to load");

		TiXmlDocument doc( docPath );
		bool loadOkay = doc.LoadFile();

		if ( !loadOkay ) {
			boost::format msg ("Could not load settings file \"%s\". Error=\"%s\".");
			msg % docPath;
			msg % doc.ErrorDesc();

			throw settings_load_error (boost::str (msg).c_str());
		}
		
		TiXmlElement* root = doc.RootElement( );
		assert ( root );
		if ( !aconnect::util::equals(root->Value(), SettingsTags::RootElement, false)  ) 
			throw settings_load_error ("Invalid setting root element");
		
		if (firstLoad_)
		{
			TiXmlElement* serverElem = root->FirstChildElement (SettingsTags::ServerElement);
			assert (serverElem);
			if ( !serverElem ) 
				throw settings_load_error ("Cannot find <server> element");

			// server setup
			loadServerSettings (serverElem);

			TiXmlElement* logElement = serverElem->FirstChildElement (SettingsTags::LogElement);
			if ( !serverElem ) 
				throw settings_load_error ("Cannot find <log> element");

			// logger setup
			loadLoggerSettings (logElement);
		} 
		else 
		{
			// clear directories info
			directories_.clear();
		}
		
		TiXmlElement* directoryElem = root->FirstChildElement (SettingsTags::DirectoryElement);
		if ( !directoryElem ) 
			throw settings_load_error ("At least one <directory> element must be");

		std::vector <DirectorySettings> directoriesList;
		do {
			directoriesList.push_back ( loadDirectory (directoryElem));
			directoryElem = directoryElem->NextSiblingElement(SettingsTags::DirectoryElement);
		
		} while (directoryElem);

		std::vector <DirectorySettings>::iterator it = directoriesList.end();
		for (it = directoriesList.begin(); 
			it != directoriesList.end() && it->name != rootDirName_; ++it );

		if (it == directoriesList.end())
			throw settings_load_error ("There is no <directory> record with name \"%s\"", 
				rootDirName_.c_str());

		if (it->realPath.empty())
			throw settings_load_error ("Empty path in root <directory> record");
		
		// work with filesystem
		try
		{
			fs::path rootPath (it->realPath, fs::portable_directory_name);
			if ( !fs::exists (rootPath) )
				throw settings_load_error ("Incorrect path in root <directory> record - path does not exist");
			if ( !fs::is_directory (rootPath) )
				throw settings_load_error ("Incorrect path in root <directory> record - target is not a directory");

			it->realPath = rootPath.directory_string();
			it->virtualPath = detail::Slash;

			// try to load local directory configuration
			fs::path dirConfigFile = fs::path (it->realPath, fs::native) / fs::path(directoryConfigFile_, fs::portable_file_name);
			tryLoadLocalSettings (dirConfigFile.string(), *it);

			// register root
			directories_ [it->virtualPath] = *it;

			fillDirectoriesMap (directoriesList, it);

			firstLoad_ = false;

		} catch (fs::basic_filesystem_error<fs::path> &err) {
			throw settings_load_error (
				"Directories info loading failed - 'basic_filesystem_error' caught: %s, "
				"system error code: %d, path 1: %s, path 2: %s", 
				err.what(), err.system_error(),
				err.path1().string().c_str(),
				err.path2().string().c_str()); 

		} catch (fs::filesystem_error &err) {
			throw settings_load_error (
				"Directories info loading failed - 'filesystem_error' caught: %s, "
				"system error code: %d", 
				err.what(), err.system_error());

		} catch (std::exception &ex)  {
			throw settings_load_error ("Directories info loading failed - exception [%s]: %s", 
				typeid(ex).name(), ex.what());;

		} catch (...)  {
			throw settings_load_error ("Directories info loading failed - unknown exception caught");
		}
	}

	bool HttpServerSettings::loadIntAttribute (class TiXmlElement* elem, 
		aconnect::string_constptr attr, int &value) 
	{
		int n;
		if ( elem->QueryIntAttribute (attr, &n) != TIXML_SUCCESS )
			return false;
			
		value = n;
		return true;
	}

	bool HttpServerSettings::loadBoolAttribute (class TiXmlElement* elem, 
		aconnect::string_constptr attr, bool &value) 
	{
		aconnect::string s;
		if ( elem->QueryValueAttribute( attr, &s) != TIXML_SUCCESS) 
			return false;

		value = aconnect::util::equals (s, SettingsTags::BooleanTrue);
		return true;
	}


	void HttpServerSettings::fillDirectoriesMap (std::vector <DirectorySettings>& directoriesList, 
											 std::vector <DirectorySettings>::iterator parent)
	{
		using namespace aconnect;
		
		
		std::vector <DirectorySettings>::iterator childIter;
		for (childIter = directoriesList.begin(); childIter != directoriesList.end(); ++childIter ) 
		{
			if (childIter->parentName == parent->name) 
			{
				if (childIter->virtualPath.empty())
					throw settings_load_error ("Empty <virtual-path> for nested directory: %s", 
						childIter->name.c_str());
				string virtualPathInit = childIter->virtualPath;

				childIter->virtualPath = parent->virtualPath + childIter->virtualPath;
				if (childIter->virtualPath.substr(childIter->virtualPath.length() - 1) != detail::Slash)
					childIter->virtualPath += detail::Slash;

				// get real path
				fs::path childPath;
				if (!childIter->realPath.empty()) {
					childPath = fs::path (childIter->realPath, fs::portable_directory_name);
					childIter->isLinkedDirectory = true;
					childIter->relativePath = virtualPathInit;
				} else {
					childPath = fs::complete (fs::path (childIter->relativePath, fs::portable_directory_name),
										fs::path (parent->realPath, fs::native));
				}
				
				if ( !fs::exists (childPath) )
					throw settings_load_error ("Incorrect path in <directory> record - path does not exist,\
											   directory: %s", childIter->name.c_str());
				if ( !fs::is_directory (childPath) )
					throw settings_load_error ("Incorrect path in <directory> record - target is not a directory,\
											   directory: %s", childIter->name.c_str());

				childIter->realPath = childPath.directory_string();
				
				// try to load local directory configuration
				fs::path dirConfigFile = fs::path (childIter->realPath, fs::native) / fs::path(directoryConfigFile_, fs::portable_file_name);

				tryLoadLocalSettings (dirConfigFile.string(), *childIter);

				// copy properties
				if (childIter->browsingEnabled == -1)
					childIter->browsingEnabled = parent->browsingEnabled;

				if (childIter->charset.empty())
					childIter->charset = parent->charset;

				if (childIter->fileTemplate.empty())	childIter->fileTemplate = parent->fileTemplate;
				if (childIter->directoryTemplate.empty())	childIter->directoryTemplate = parent->directoryTemplate;
				if (childIter->parentDirectoryTemplate.empty())	childIter->parentDirectoryTemplate = parent->parentDirectoryTemplate;
				if (childIter->virtualDirectoryTemplate.empty())	childIter->virtualDirectoryTemplate = parent->virtualDirectoryTemplate;
				if (childIter->headerTemplate.empty())	childIter->headerTemplate = parent->headerTemplate;
				if (childIter->footerTemplate.empty())	childIter->footerTemplate = parent->footerTemplate;

				// fill default documents list
				default_documents_vector defDocsList = parent->defaultDocuments;

				default_documents_vector::iterator iter = childIter->defaultDocuments.begin(), removeIter;
				while (iter != childIter->defaultDocuments.end()) 
				{
					if (iter->first && std::find (defDocsList.begin(), defDocsList.end(), *iter) == defDocsList.end()) {
						defDocsList.push_back(*iter);
					
					} else if (!iter->first) {
						removeIter = std::find (defDocsList.begin(), defDocsList.end(), std::make_pair(true, iter->second));
						if (removeIter == defDocsList.end())
							throw settings_load_error ("Cannot remove default document record \"%s\", in directory: %s - "
								"it is not declared in parent directory record.", 
									iter->second.c_str(), 
									childIter->name.c_str());
						else
							defDocsList.erase(removeIter);
					}
					iter++;
				}
				childIter->defaultDocuments = defDocsList;
				
				// copy handlers registraton
				directory_handlers_map::iterator handlerIter;
				for (handlerIter = parent->handlers.begin(); 
						handlerIter != parent->handlers.end(); ++handlerIter) 
				{
					if (childIter->handlers.find(handlerIter->first) == childIter->handlers.end())
						childIter->handlers.insert (*handlerIter);
				}

				directories_[childIter->virtualPath] = *childIter;
				
				fillDirectoriesMap (directoriesList, childIter);
			}

		}

	}

	void HttpServerSettings::tryLoadLocalSettings (aconnect::string_constref filePath, DirectorySettings& dirInfo) 
		throw (settings_load_error)
	{

		if (fs::exists (filePath))
		{
			TiXmlDocument doc( filePath.c_str() );

			if ( !doc.LoadFile() ) {
				boost::format msg ("Could not load local directory config file \"%s\". Error=\"%s\".");
				msg % filePath;
				msg % doc.ErrorDesc();

				throw settings_load_error (boost::str (msg).c_str());
			}

			TiXmlElement* root = doc.RootElement( );

			if ( !aconnect::util::equals(root->Value(), SettingsTags::DirectoryElement, false)  ) 
				throw settings_load_error ("Invalid local directory config file root element, file: %s",
					filePath.c_str());

			loadLocalDirectorySettings (root, dirInfo);
		}
	}

	void HttpServerSettings::loadServerSettings (TiXmlElement* serverElem) throw (settings_load_error)
	{
		using namespace aconnect;
		int intValue = 0, getAttrRes;
		string_constptr strValue;
		string stringValue;

		// version
		strValue = serverElem->Attribute (SettingsTags::VersionAttr);
		if (!util::isNullOrEmpty(strValue))
			serverVersion_ = strValue;

		// port
		getAttrRes = serverElem->QueryIntAttribute (SettingsTags::PortAttr, &intValue );
		if ( getAttrRes != TIXML_SUCCESS ) 
			throw settings_load_error ("Port number loading failed");
		port_ = intValue;

		// command port
		getAttrRes = serverElem->QueryIntAttribute (SettingsTags::CommandPortAttr, &intValue );
		if ( getAttrRes != TIXML_SUCCESS ) 
			throw settings_load_error ("Command port number loading failed");
		commandPort_ = intValue;

		// workers count - OPTIONAL
		loadIntAttribute (serverElem, SettingsTags::WorkersCountAttr, settings_.workerLifeTime);

		// pooling - OPTIONAL
		loadBoolAttribute (serverElem, SettingsTags::PoolingEnabledAttr, settings_.enablePooling);

		// worker life time - OPTIONAL
		loadIntAttribute (serverElem, SettingsTags::WorkerLifeTimeAttr, settings_.workerLifeTime);
		
		// read timeouts
		getAttrRes = serverElem->QueryIntAttribute (SettingsTags::ServerSocketTimeoutAttr, &intValue );
		settings_.socketWriteTimeout = 
			settings_.socketReadTimeout = 
				(getAttrRes == TIXML_SUCCESS ? intValue : defaults::ServerSocketTimeout);

		// load keep-alive mode
		loadBoolAttribute (serverElem, SettingsTags::KeepAliveEnabledAttr, enableKeepAlive_);
		
		getAttrRes = serverElem->QueryIntAttribute (SettingsTags::KeepAliveTimeoutAttr, &intValue );
		keepAliveTimeout_ = (getAttrRes == TIXML_SUCCESS ? intValue : defaults::KeepAliveTimeout);
			
		getAttrRes = serverElem->QueryIntAttribute (SettingsTags::CommandSocketTimeoutAttr, &intValue );
		commandSocketTimeout_ = (getAttrRes == TIXML_SUCCESS ? intValue : defaults::CommandSocketTimeout);

		// directory configuration file
		strValue = serverElem->Attribute (SettingsTags::DirectoryConfigFileAttr);
		if (!util::isNullOrEmpty(strValue))
			directoryConfigFile_ = strValue;

		// read HTTP settings
		strValue = serverElem->Attribute (SettingsTags::ResponseBufferSizeAttr);
		if (!util::isNullOrEmpty(strValue))
			responseBufferSize_ = boost::lexical_cast<size_t> (strValue);

		strValue = serverElem->Attribute (SettingsTags::MaxChunkSizeAttr);
		if (!util::isNullOrEmpty(strValue))
			maxChunkSize_ = boost::lexical_cast<size_t> (strValue);

		// root directory
		strValue = serverElem->Attribute( SettingsTags::RootAttr );
		if ( util::isNullOrEmpty(strValue) ) 
			throw settings_load_error ("Invalid root directory name");
		setRoot (strValue);

		TiXmlElement* mimeTypesElement = serverElem->FirstChildElement (SettingsTags::MimeTypesElement);
		if (!mimeTypesElement)
			throw settings_load_error ("<%s> not found in server settings", SettingsTags::MimeTypesElement);

		loadMimeTypes (mimeTypesElement);

		// load global handlers
		TiXmlElement* handlersElem = serverElem->FirstChildElement (SettingsTags::HandlersElement);
		if (handlersElem)
			loadHandlers (handlersElem);
	}

	void HttpServerSettings::loadLoggerSettings (TiXmlElement* logElement) throw (settings_load_error)
	{
		using namespace aconnect;
		assert (logElement);
		string_constptr strValue;

		// load level
		strValue = logElement->Attribute( SettingsTags::LogLevelAttr );
		if (_stricmp (strValue, Log::ErrorMsg) == 0)
			logLevel_ = Log::Error;
		else if (_stricmp (strValue, Log::WarningMsg) == 0)
			logLevel_ = Log::Warning;
		else if (_stricmp (strValue, Log::InfoMsg) == 0)
			logLevel_ = Log::Info;
		else 
			logLevel_ = Log::Debug;
		
		int intValue = 0, getAttrRes;
		
		// load max file size
		getAttrRes = logElement->QueryIntAttribute (SettingsTags::MaxFileSizeAttr, &intValue );
		if (getAttrRes == TIXML_SUCCESS)
			maxLogFileSize_ = intValue;

		TiXmlElement* pathElement = logElement->FirstChildElement (SettingsTags::PathElement);
		assert (pathElement);

		strValue = pathElement->GetText();
		if ( util::isNullOrEmpty(strValue) ) 
			throw settings_load_error ("Invalid log file template");
		
		logFileTemplate_ = strValue;
	}


	DirectorySettings HttpServerSettings::loadDirectory (TiXmlElement* directoryElem) throw (settings_load_error)
	{
		using namespace aconnect;
		assert (directoryElem);
		
		DirectorySettings ds;
		string strValue;
		string_constptr strPtrValue;
		int getAttrRes = 0;

		// load name
		getAttrRes = directoryElem->QueryValueAttribute( SettingsTags::NameAttr, &ds.name);
		if (getAttrRes != TIXML_SUCCESS)
			throw settings_load_error ("Directory does not have \"%s\" attribute",
				SettingsTags::NameAttr);
		
		// load browsing-enabled
		if (directoryElem->QueryValueAttribute( SettingsTags::BrowsingEnabledAttr, &strValue) == TIXML_SUCCESS) 
			ds.browsingEnabled = util::equals(strValue, SettingsTags::BooleanTrue) ? 1 : 0;

		// load charset
		if (directoryElem->QueryValueAttribute( SettingsTags::CharsetAttr, &strValue) == TIXML_SUCCESS) 
			ds.charset = strValue;
		
		// load parent
		getAttrRes = directoryElem->QueryValueAttribute( SettingsTags::ParentAttr, &strValue);
		if (getAttrRes == TIXML_SUCCESS) 
			ds.parentName = strValue;

		// path
		bool realPathDefined = false;
		TiXmlElement* pathElement = directoryElem->FirstChildElement (SettingsTags::PathElement);
		if (pathElement) {
			strPtrValue = pathElement->GetText();
			realPathDefined = true;

			if ( util::isNullOrEmpty(strPtrValue) ) 
				throw settings_load_error ("Empty path attribute for directory: %s", ds.name.c_str());
			ds.realPath = strPtrValue;
			
			updateAppLocationInPath (ds.realPath);
		}

		// virtual-path
		pathElement = directoryElem->FirstChildElement (SettingsTags::VirtualPathElement);
		if (pathElement) {
			strPtrValue = pathElement->GetText();
			if ( !util::isNullOrEmpty(strPtrValue) )
				ds.virtualPath = strPtrValue;
		}

		// relative-path
		pathElement = directoryElem->FirstChildElement (SettingsTags::RelativePathElement);
		if (pathElement) {
			if (realPathDefined)
				throw settings_load_error ("<%s> and <%s> must not be defined together,\
						directory: %s",
						SettingsTags::PathElement,
						SettingsTags::RelativePathElement,
						ds.name.c_str());
			
			strPtrValue = pathElement->GetText();
			if ( !util::isNullOrEmpty(strPtrValue) ) 
				ds.relativePath = strPtrValue;
		}


		TiXmlElement* fileTemplate = directoryElem->FirstChildElement (SettingsTags::FileTemplateElement);
		TiXmlElement* directoryTemplate = directoryElem->FirstChildElement (SettingsTags::DirectoryTemplateElement);
		TiXmlElement* virtualDirectoryTemplate = directoryElem->FirstChildElement (SettingsTags::VirtualDirectoryTemplateElement);
		TiXmlElement* parentDirectoryTemplate = directoryElem->FirstChildElement (SettingsTags::ParentDirectoryTemplateElement);

		if ( (!fileTemplate && directoryTemplate) || (fileTemplate && !directoryTemplate))
			throw settings_load_error ("<directory-template> and <file-template> should be defined together, directory: %s", ds.name.c_str());

		if (ds.browsingEnabled == 1 && !fileTemplate)
			throw settings_load_error ("<directory-template> and <file-template> must be defined together,\
									   when browsing enabled, directory: %s", ds.name.c_str());

		if (fileTemplate && directoryTemplate) {
			strPtrValue = fileTemplate->GetText();		if (strPtrValue) ds.fileTemplate = strPtrValue;
			strPtrValue = directoryTemplate->GetText();	if (strPtrValue) ds.directoryTemplate = strPtrValue;
		}
		if (virtualDirectoryTemplate && (strPtrValue = virtualDirectoryTemplate->GetText()) ) 
			ds.virtualDirectoryTemplate = strPtrValue;
		
		if (parentDirectoryTemplate && (strPtrValue = parentDirectoryTemplate->GetText())) 
			ds.parentDirectoryTemplate = strPtrValue;
		

		// add tabulators
		algo::replace_all (ds.fileTemplate, SettingsTags::TabulatorMark, "\t");
		algo::replace_all (ds.directoryTemplate, SettingsTags::TabulatorMark, "\t");
		algo::replace_all (ds.virtualDirectoryTemplate, SettingsTags::TabulatorMark, "\t");
		algo::replace_all (ds.parentDirectoryTemplate, SettingsTags::TabulatorMark, "\t");


		TiXmlElement* headerTemplate = directoryElem->FirstChildElement (SettingsTags::HeaderTemplateElement);
		if (headerTemplate) {
			strPtrValue = headerTemplate->GetText(); 
			if (strPtrValue) 
				ds.headerTemplate = strPtrValue;
		}

		TiXmlElement* footerTemplate = directoryElem->FirstChildElement (SettingsTags::FooterTemplateElement);
		if (footerTemplate) {
			strPtrValue = footerTemplate->GetText(); 
			if (strPtrValue) 
				ds.footerTemplate = strPtrValue;
		}
		
		loadLocalDirectorySettings (directoryElem, ds);

		return ds;
	}

	void HttpServerSettings::loadLocalDirectorySettings (class TiXmlElement* directoryElem, DirectorySettings& dirInfo) 
		throw (settings_load_error) 
	{
		// load default documents
		TiXmlElement* defDocumentsElem = directoryElem->FirstChildElement (SettingsTags::DefaultDocumentsElement);
		if (defDocumentsElem)
			loadDirectoryIndexDocuments (defDocumentsElem, dirInfo);
		
		// load directory handlers
		TiXmlElement* handlersElem = directoryElem->FirstChildElement (SettingsTags::HandlersElement);
		if (handlersElem)
			loadDirectoryHandlers (handlersElem, dirInfo);

		// load directory mappings
		TiXmlElement* mappings = directoryElem->FirstChildElement (SettingsTags::MappingsElement);
		if (mappings)
			loadDirectoryMappings (mappings, dirInfo);

	}


	void HttpServerSettings::updateAppLocationInPath (aconnect::string &pathStr) const 
	{
		if ( pathStr.find(SettingsTags::AppPathMark) != aconnect::string::npos)
				algo::replace_first (pathStr, SettingsTags::AppPathMark, appLocaton_);
	}

	void HttpServerSettings::loadMimeTypes (class TiXmlElement* mimeTypesElement) throw (settings_load_error)
	{
		aconnect::string_constptr filePath =
			mimeTypesElement->Attribute(SettingsTags::FileAttr);

		if ( filePath )
		{
			aconnect::string filePathStr (filePath);
			
			updateAppLocationInPath (filePathStr);
		
			TiXmlDocument doc;
			bool loaded = doc.LoadFile (filePathStr);

			if ( !loaded ) {
				boost::format msg ("Could not load MIME-types definition file \"%s\". Error=\"%s\".");
				msg % filePathStr;
				msg % doc.ErrorDesc();
				throw settings_load_error (boost::str (msg).c_str());
			}

			TiXmlElement* root = doc.RootElement( );
			assert ( root );
			if ( !aconnect::util::equals (root->Value(), SettingsTags::MimeTypesElement, false) ) 
				throw settings_load_error ("Invalid root element in MIME-types definition file");

			loadMimeTypes (root);
		}

		// load sub-nodes

		TiXmlElement* typeElem = mimeTypesElement->FirstChildElement (SettingsTags::TypeElement);
		if ( !typeElem ) 
			return;
		
		aconnect::string_constptr strPtrValue;
		aconnect::string strValue;
		int getAttrRes = 0;

		do {
			// load name
			getAttrRes = typeElem->QueryValueAttribute( SettingsTags::ExtAttr, &strValue);
			if (getAttrRes == TIXML_NO_ATTRIBUTE)
				throw settings_load_error ("<%s> does not have \"%s\" attribute",
					SettingsTags::TypeElement, 
					SettingsTags::ExtAttr);

			strPtrValue = typeElem->GetText(); 
			if (strPtrValue) 
				mimeTypes_ [strValue] = strPtrValue;

			typeElem = typeElem->NextSiblingElement (SettingsTags::TypeElement);

		} while (typeElem);
	}

	void HttpServerSettings::loadHandlers (class TiXmlElement* handlersElement) 
		throw (settings_load_error)
	{
		using namespace aconnect;

		string_constptr strPtrValue;
		string handlerName, strValue;
		int getAttrRes;
		TiXmlElement* childElem;

		TiXmlElement* handlerElem = handlersElement->FirstChildElement (SettingsTags::HandlerItemElement);
		
		while (handlerElem) 
		{
			HandlerInfo info;

			getAttrRes = handlerElem->QueryValueAttribute( SettingsTags::NameAttr, &handlerName);
			if (getAttrRes == TIXML_NO_ATTRIBUTE)
				throw settings_load_error ("Global <%s> does not have \"%s\" attribute - it is required",
					SettingsTags::HandlerItemElement, 
					SettingsTags::NameAttr);

			if ((handlerElem->QueryValueAttribute( SettingsTags::DefaultExtAttr, &strValue) == TIXML_SUCCESS)) {
				if (strValue.empty())
					throw settings_load_error ("Handler \"%s\" has empty \"%s\" attribute",
						handlerName.c_str(), 
						SettingsTags::DefaultExtAttr);
				info.defaultExtension = strValue;
			}

			childElem = handlerElem->FirstChildElement(SettingsTags::PathElement);
			if (!childElem)
				throw settings_load_error ("Handler \"%s\" has no <%s> element ", 
					handlerName.c_str(), 
					SettingsTags::PathElement);

			strPtrValue = childElem->GetText();
			if (util::isNullOrEmpty(strPtrValue))
				throw settings_load_error ("Handler \"%s\" has empty <%s> element ", 
					handlerName.c_str(), 
					SettingsTags::PathElement);

			info.pathToLoad = strPtrValue;

			// load parameters
			childElem = handlerElem->FirstChildElement (SettingsTags::ParameterElement);
			
			while (childElem) 
			{
				getAttrRes = childElem->QueryValueAttribute( SettingsTags::NameAttr, &strValue);
				if (getAttrRes == TIXML_NO_ATTRIBUTE)
					throw settings_load_error ("<%s> for handler \"%s\" have no \"%s\" attribute",
						SettingsTags::ParameterElement, 
						handlerName.c_str(), 
						SettingsTags::NameAttr );

				strPtrValue = childElem->GetText();
				if (NULL != strPtrValue)
					info.params[strValue] = strPtrValue;

				childElem = childElem->NextSiblingElement (SettingsTags::HandlerItemElement);
			}
			
			registerHandler (handlerName, info);

			handlerElem = handlerElem->NextSiblingElement (SettingsTags::ParameterElement);
		}
	}


	void HttpServerSettings::registerHandler (aconnect::string_constref handlerName, HandlerInfo& info) 
		throw (settings_load_error)
	{
		aconnect::string pathToLoad (info.pathToLoad);
		updateAppLocationInPath (pathToLoad);

		if (registeredHandlers_.find (handlerName) != registeredHandlers_.end() )
			throw settings_load_error ("Handler \"%s\" has been already loaded", handlerName.c_str());

#if defined (WIN32)		
		HMODULE dll = ::LoadLibraryA (pathToLoad.c_str());

		if (NULL == dll)
			throw settings_load_error ("Handler loading failed, library: %s", pathToLoad.c_str());

		info.processRequestFunc = ::GetProcAddress (dll, SettingsTags::ProcessRequestFunName);
		if (!info.processRequestFunc) 
			throw settings_load_error ("Request processing function loading failed, "
				"library: %s, error code: %d", pathToLoad.c_str(), ::GetLastError());

		info.initFunc = ::GetProcAddress (dll, SettingsTags::InitFunName);
		if (!info.initFunc) 
			throw settings_load_error ("Handler initialization function loading failed, "
				"library: %s, error code: %d", pathToLoad.c_str(), ::GetLastError());
#else
		void * dll = dlopen (pathToLoad.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND );
		if (NULL == dll) {
			aconnect::string_constptr errorMsg = dlerror (); 
			throw settings_load_error ("Handler loading failed, library: %s, error: %s", 
				pathToLoad.c_str(), errorMsg ? errorMsg : "Unknown error");
		}

		info.processRequestFunc = dlsym (dll, SettingsTags::ProcessRequestFunName);
		if (!info.processRequestFunc) 
			throw settings_load_error ("Request processing function loading failed, "
				"library: %s, error: %s", pathToLoad.c_str(), dlerror());
		
		info.initFunc = dlsym (dll, SettingsTags::InitFunName);
		if (!info.initFunc) 
			throw settings_load_error ("Handler initialization function loading failed, "
				"library: %s, error: %s", pathToLoad.c_str(), dlerror());

#endif
		
		registeredHandlers_[handlerName] = info;
	}

	void HttpServerSettings::loadDirectoryIndexDocuments (class TiXmlElement* documentsElement, DirectorySettings& ds) 
		throw (settings_load_error)
	{
		aconnect::string_constptr strPtrValue;

		TiXmlElement* elem = documentsElement->FirstChildElement (SettingsTags::AddElement);
		while (elem) {
			if ( (strPtrValue = elem->GetText()) ) 
				ds.defaultDocuments.push_back (std::make_pair (true, strPtrValue));

			elem = elem->NextSiblingElement (SettingsTags::AddElement);
		}
		
		elem = documentsElement->FirstChildElement(SettingsTags::RemoveElement);
		while (elem) {
			if ( (strPtrValue = elem->GetText()) ) 
				ds.defaultDocuments.push_back (std::make_pair (false, strPtrValue));
			elem = elem->NextSiblingElement (SettingsTags::RemoveElement);
		}
	}

	void HttpServerSettings::loadDirectoryHandlers (class TiXmlElement* handlersElement, DirectorySettings& dirInfo) 
		throw (settings_load_error)
	{
		aconnect::string ext, name;
		int getAttrRes;

		TiXmlElement* item = handlersElement->FirstChildElement (SettingsTags::RegisterElement);
		
		while (item) 
		{
			getAttrRes = item->QueryValueAttribute( SettingsTags::NameAttr, &name);
			if (getAttrRes == TIXML_NO_ATTRIBUTE)
				throw settings_load_error ("<%s> does not have \"%s\" attribute, directory: %s",
					SettingsTags::RegisterElement, SettingsTags::NameAttr, dirInfo.name.c_str());
			
			if (registeredHandlers_.find(name) == registeredHandlers_.end())
				throw settings_load_error ("Handler \"%s\" is not registered, directory: %s",
					name.c_str(), dirInfo.name.c_str());
			
			const HandlerInfo& info = registeredHandlers_[name];

			getAttrRes = item->QueryValueAttribute( SettingsTags::ExtAttr, &ext);
			if (info.defaultExtension.empty() && (getAttrRes == TIXML_NO_ATTRIBUTE || ext.empty() ))
				throw settings_load_error ("Handler \"%s\" has not link to file extension, directory: %s",
					name.c_str(), dirInfo.name.c_str());
			else if (ext.empty())
				ext = info.defaultExtension;

			dirInfo.handlers.insert ( std::make_pair (ext, info.processRequestFunc) );

			item = item->NextSiblingElement (SettingsTags::RegisterElement);
		}

	}

	void HttpServerSettings::loadDirectoryMappings (class TiXmlElement* mappingsElement, DirectorySettings& dirInfo) 
		throw (settings_load_error)
	{
		
		TiXmlElement* item = mappingsElement->FirstChildElement (SettingsTags::RegisterElement);

		while (item) 
		{
			TiXmlElement* reElem = item->FirstChildElement(SettingsTags::RegexElement);
			if (NULL == reElem)
				throw settings_load_error ("<%s> does not have <%s> child element, directory: %s",
					SettingsTags::RegisterElement, SettingsTags::RegexElement, dirInfo.name.c_str());

			aconnect::string_constptr re = reElem->GetText();

			if ( aconnect::util::isNullOrEmpty(re) )
				throw settings_load_error ("<%s> element is empty, directory: %s",
					SettingsTags::RegexElement, dirInfo.name.c_str());

			TiXmlElement* urlElem = item->FirstChildElement(SettingsTags::UrlElement);
			if (NULL == urlElem)
				throw settings_load_error ("<%s> does not have <%s> child element, directory: %s",
					SettingsTags::RegisterElement, SettingsTags::UrlElement, dirInfo.name.c_str());


			aconnect::string_constptr url = urlElem->GetText();
			if ( aconnect::util::isNullOrEmpty(url) )
				throw settings_load_error ("<%s> element is empty, directory: %s",
					SettingsTags::UrlElement, dirInfo.name.c_str());


			dirInfo.mappings.push_back( std::make_pair(boost::regex(re), url));

			item = item->NextSiblingElement (SettingsTags::RegisterElement);
		}


	}

	void HttpServerSettings::initHandlers ()
	{
		assert (logger_);
		global_handlers_map::const_iterator iter;
		for (iter = registeredHandlers_.begin(); iter != registeredHandlers_.end(); ++iter)
		{
			bool inited = reinterpret_cast<init_handler_function> (iter->second.initFunc) 
				(iter->second.params, this);
			
			if (!inited)
				throw settings_load_error ("Handler \"%s\" initialization failed failed",
					iter->first.c_str() );
		}
	}

} // namespace ahttp

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Belarus Belarus
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions