Click here to Skip to main content
13,087,902 members (79,951 online)
Click here to Skip to main content

Stats

45.1K views
1.3K downloads
63 bookmarked
Posted 1 Jun 2008

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

, 1 Jun 2008
This article describes portable networking library (ahttp) and small HTTP server - result of modern C++ programming approaches investigation
ahttpserver_demo
ahttpserver.exe
boost_python-vc80-mt-1_34_1.dll
python_handler.dll
web
images
icon_dir.gif
icon_file.gif
icon_virtual_dir.gif
python
favicon.ico
print.pyhtml
ahttplib
aconnect
ahttp
http_header_read_check.inl
tinyxml
ahttpserver
out
server.config.ubuntu
web
images
icon_dir.gif
icon_file.gif
icon_virtual_dir.gif
python
python_handler
wrappers.inl
makefile
module.inl
server.config.win-debug
server.config.win-release
favicon.ico
print.pyhtml
#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 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)

Share

About the Author

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

You may also be interested in...

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