Click here to Skip to main content
Click here to Skip to main content
 

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

By , 1 Jun 2008
 
ahttpserver_demo.zip
ahttpserver_demo
ahttpserver.exe
boost_python-vc80-mt-1_34_1.dll
python_handler.dll
web
favicon.ico
images
icon_dir.gif
icon_file.gif
icon_virtual_dir.gif
python
print.pyhtml
ahttpserver_src.zip
ahttplib
aconnect
ahttp
http_header_read_check.inl
tinyxml
ahttpserver
makefile
out
server.config.ubuntu
server.config.win-debug
server.config.win-release
web
favicon.ico
images
icon_dir.gif
icon_file.gif
icon_virtual_dir.gif
python
print.pyhtml
python_handler
module.inl
wrappers.inl
#include "aconnect/boost_format_safe.hpp"

#include <iostream>
#include <assert.h>

#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/timer.hpp>


#if defined (WIN32)
#	include <signal.h>
#elif defined (__GNUC__)
#	include <sys/signal.h>
#endif  //__GNUC__

// aconnect
#include "ahttplib.hpp"
#include "aconnect/util.hpp"

#include "constants.hpp"

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

// globals
namespace Global
{
	fs::path appPath;
	aconnect::string settingsFilePath;
	
	ahttp::HttpServerSettings globalSettings;
	aconnect::FileLogger logger;
	aconnect::Server httpServer;
	aconnect::Server commandServer;
}

// declarations
void init (aconnect::string_constptr relativeAppPath);
void destroy ();
void loadSettings ();
void initHandlers ();
void initLogger ();
void processException (aconnect::string_constptr message, int exitCode);
void processSignal (int sig); 
aconnect::socket_type findRunningServer();
void processCommand (const aconnect::ClientInfo& client);
void sendCommand (const aconnect::socket_type sock, aconnect::string_constref command);
void processServerCommand (aconnect::string command);

// definitions
void init (aconnect::string_constptr relativeAppPath) 
{
	signal (SIGINT, processSignal); 
	signal (SIGTERM, processSignal);
	// run-time errors processing
	signal (SIGSEGV, processSignal);
	signal (SIGFPE, processSignal);
	signal (SIGILL, processSignal);
	signal (SIGABRT, processSignal); 

#ifdef WIN32
	signal (SIGBREAK, processSignal);
#endif

	try {
		Global::appPath = aconnect::util::getAppLocation (relativeAppPath);
		
		fs::path configFilePath = fs::complete (Settings::ConfigFileName, Global::appPath.branch_path());
		Global::settingsFilePath = configFilePath.file_string();
		
	} catch (std::exception &err) {
		processException (err.what(), ReturnCodes::InitizalizationFailed);
	}

    // init socket library
	try {
		aconnect::Initializer::init();
	} catch (std::exception &ex) {
		processException (ex.what(), ReturnCodes::InitizalizationFailed);
	}
}

void destroy () 
{
	try {
		// force stop
		Global::httpServer.stop();

        // unload socket library
		aconnect::Initializer::destroy ();

	} catch (std::exception &ex) {
		std::cerr << ex.what() << std::endl;
		Global::logger.error(ex);
	}
    
	// close logger
    Global::logger.info ( "Server stopped" );
    Global::logger.destroy ();
}


void loadSettings ()
{
	try 
	{
		Global::globalSettings.setAppLocaton ( 
			fs::path(Global::appPath).remove_leaf().directory_string().c_str() );
		
		Global::globalSettings.load ( Global::settingsFilePath.c_str() );
		
	} catch (ahttp::settings_load_error &ex) {
		processException (ex.what(), ReturnCodes::SettingsLoadFailed);
	}

}

void initHandlers ()
{
	try 
	{
		Global::globalSettings.initHandlers();
	
	} catch (ahttp::settings_load_error &ex) {
		processException (ex.what(), ReturnCodes::SettingsLoadFailed);
	}

}

void initLogger () {
	using namespace Global;
	using namespace aconnect;

	try {
		// init logger
		string logFileTemplate = globalSettings.logFileTemplate();
		Global::globalSettings.updateAppLocationInPath (logFileTemplate);

		fs::path logFilesDir = fs::path (logFileTemplate, fs::native).branch_path();
		if (!fs::exists (logFilesDir))
			fs::create_directories(logFilesDir);


		logger.init (globalSettings.logLevel(), logFileTemplate.c_str(), globalSettings.maxLogFileSize());
		logger.info ( "Server started" );

	} catch (std::exception &ex) {
		processException (ex.what(), ReturnCodes::LoggerSetupFailed);
	}
	

}

void processException (aconnect::string_constptr message, int exitCode) 
{
	std::cerr << message << std::endl;
	Global::logger.error ("Exception occurred: %s", message);
	exit (exitCode);
}


void processSignal (int sig) {
	
	Global::logger.error ("Server retrieved signal: %d",sig);
	
	destroy ();
	exit (ReturnCodes::ForceStopped);
}

aconnect::socket_type findRunningServer() 
{
	using namespace aconnect;
	socket_type clientSock = util::createSocket (AF_INET);
	
	struct sockaddr_in local;
	util::zeroMemory (&local, sizeof( local ));

	local.sin_family = AF_INET;
	local.sin_port = htons ( Global::globalSettings.commandPort() );
	local.sin_addr.s_addr = inet_addr ( "127.0.0.1" );

	int connectRes = connect (clientSock, (struct sockaddr*) &local, sizeof( local ) );
	if ( connectRes != 0 )
		clientSock = INVALID_SOCKET;
	
	return clientSock;
}



void processCommand (const aconnect::ClientInfo& client)
{
	using namespace aconnect;
	string command = Settings::CommandUnknown, response;
	bool stopServer = false;
	int retCode = ReturnCodes::Success;

	try
	{
		string endMark = Settings::EndMark;

		EndMarkSocketStateCheck check (endMark);
		command = client.getRequest (check);
		algo::erase_tail (command, (int) endMark.length());
		
		if (util::equals (command, Settings::CommandStop)) {
			response = "Stopped";
			stopServer = true;
			
		} else if (util::equals (command, Settings::CommandStat)) {
			const int buffSize = 1024;
			char_type buff[buffSize];

			int formattedCount = snprintf (buff, buffSize, 
				Settings::StatisticsFormat,
				(long) ahttp::HttpServer::RequestsCount,
				(long) Global::httpServer.currentWorkersCount(),
				(long) Global::httpServer.currentPendingWorkersCount());
			
			response.append (buff, util::min2(formattedCount, buffSize));
		
		} else if (util::equals (command, Settings::CommandReload)) {

			response = "Directories settings reloaded";
			try 
			{
				Global::httpServer.stop (true);
				Global::globalSettings.load ( Global::settingsFilePath.c_str() );
				Global::httpServer.start();

			} catch (ahttp::settings_load_error &ex) {
				response = string ("Settings reload failed: ") + ex.what();

				stopServer = true;
				retCode = ReturnCodes::SettingsLoadFailed;
			}
			
		} else {
			response = "Unknown command: " + command;
		}

		client.writeResponse(response + endMark);

		if (util::equals (command, Settings::CommandStop))  
		{
			destroy ();
			exit (retCode);
		}
	}
	catch (std::exception &ex)
	{
		Global::logger.error("Command processing failed, command: %s, error: %s",
			command.c_str(), ex.what() );
	}
}

void sendCommand (const aconnect::socket_type sock, aconnect::string_constref command)
{
	using namespace aconnect;
	try
	{
		string endMark = Settings::EndMark;
		util::writeToSocket(sock, command + endMark);

		EndMarkSocketStateCheck check (endMark);
		string response = util::readFromSocket(sock, check);
		
		algo::erase_tail (response, (int) endMark.length());
		std::cout << response << std::endl;
	}
	catch (std::exception &ex)
	{
		std::cout << "Command sending failed: " << ex.what() << std::endl;
	}
}

void processServerCommand (aconnect::string command)
{
	using namespace aconnect;
	// find running server
	socket_type sock = findRunningServer ();

	if (util::equals(command, Settings::CommandStart))
	{
		if (sock != INVALID_SOCKET) {
			std::cerr << "Server already started - 'stat' command will be sent" << std::endl;
			command = Settings::CommandStat;

		} else {
#if defined (WIN32)
			string startString = "start \"\" \"" + Global::appPath.file_string() 
				+ "\"  " + string(Settings::CommandRun);

			int res = system (startString.c_str());
			if (res == -1)
				std::cerr << "Server startup failed, errno: " << errno << std::endl;
#else
			std::cerr << "Wrong execution path - 'start' command cannot be processed" << std::endl;
#endif
			return;
		}
	}

	// write commands list
	std::cout << Settings::CommandsList << std::endl
		<< Settings::BreakLine << std::endl;

	if (sock != INVALID_SOCKET) {
		sendCommand (sock, command);
		util::closeSocket (sock);
	
	} else {
		std::cerr << "Server is not started" << std::endl;
	}
}

//////////////////////////////////////////////////////////////////////////
//		Entry point
//////////////////////////////////////////////////////////////////////////
int main (int argc, char* args[]) 
{
	using namespace aconnect;
	namespace fs = boost::filesystem;

	init (args[0]);
	
	boost::timer loadTimer;
	double settingsLoadTime = 0,
		loggerInitTime = 0,
		handlersInitTime = 0,
		serverStartupTime = 0;
	
	loadSettings ();
	settingsLoadTime = loadTimer.elapsed(); loadTimer.restart();

	string command = Settings::CommandStat;
	if (argc > 1) 
		command = args[1];
	
#if !defined (WIN32)
	if (util::equals (Settings::CommandStart, command))
		command = Settings::CommandRun;
#endif
	
	if (!util::equals (Settings::CommandRun, command)) 
	{
		processServerCommand (command);
		destroy();
		return ReturnCodes::Success;
	}

	// start server
	ScopedGuard<simple_callback>  guard (destroy);

	initLogger ();
	loggerInitTime = loadTimer.elapsed(); loadTimer.restart();

	Global::globalSettings.setLogger ( &Global::logger);
	ahttp::HttpServer::setGlobalSettings ( &Global::globalSettings);

	initHandlers ();
	handlersInitTime = loadTimer.elapsed(); loadTimer.restart();

	// init HTTP server (in child thread)
	Global::httpServer.setLog ( &Global::logger);
	Global::httpServer.init (Global::globalSettings.port(), 
		ahttp::HttpServer::processConnection, 
		Global::globalSettings.serverSettings());
	
	Global::httpServer.setErrorProcessProc (ahttp::HttpServer::processWorkerCreationError);

	// init command server
	ServerSettings cmdServerSettings;
	cmdServerSettings.socketReadTimeout = 
		cmdServerSettings.socketWriteTimeout = Global::globalSettings.commandSocketTimeout();

	Global::commandServer.setLog ( &Global::logger);
	Global::commandServer.init (Global::globalSettings.commandPort(), 
		processCommand,
		cmdServerSettings);

	try {
		util::detachFromConsole ();	

		Global::httpServer.start();
		serverStartupTime = loadTimer.elapsed(); loadTimer.restart();

		// write timing
		boost::format record ("%s: elapsed time - %f sec");
		record % "Settings load" % settingsLoadTime;
		Global::logger.info(record.str().c_str());

		record.clear(); record % "Logger initialization" % loggerInitTime;
		Global::logger.info(record.str().c_str());

		record.clear(); record % "Handlers initialization" % handlersInitTime;
		Global::logger.info(record.str().c_str());

		record.clear(); record % "Server startup" % serverStartupTime;
		Global::logger.info(record.str().c_str());

		// start command retrieving cycle
		Global::commandServer.start ();
		Global::commandServer.join();
		
	} catch (std::exception &ex)  {
		
		Global::logger.error ("Exception caught at server startup (%s): %s", 
			typeid(ex).name(), ex.what());
		
		std::cerr << "Server startup failed: " << typeid(ex).name() <<  ex.what() << std::endl;

		exit (ReturnCodes::ServerStartupFailed);
	} 

	return ReturnCodes::Success;
}

By viewing downloads associated with this article you agree to the Terms of use 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)

About the Author

Artem Kustikov
Software Developer (Senior)
Belarus Belarus
Member
No Biography provided

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 1 Jun 2008
Article Copyright 2008 by Artem Kustikov
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid