Click here to Skip to main content
15,886,519 members
Articles / Programming Languages / C++

Small and Reliable C++ HTTP Server with Complete ASP.NET Support

Rate me:
Please Sign up or sign in to vote.
4.78/5 (24 votes)
5 Feb 2009CPOL8 min read 74.1K   1.6K   81  
This article describes results of ahttpserver evolution - implementation of ASP.NET handler and many architecture improvements
/*
This file is part of [ahttp] library. 

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

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.
*/

// python_handler.cpp : Defines the entry point for the DLL application.
//

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

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

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

#include "wrappers.hpp"
#include "module.inl"

namespace Globals
{
	// constants
	const aconnect::string Param_UploadsDir = "uploads-dir";
	
	// globals
	boost::mutex LoadMutex;
    aconnect::string UploadsDirPath;

	static ahttp::HttpServerSettings *GlobalServerSettings = NULL;

	PyThreadState * MainThreadState = NULL;
	python::object	MainModule;
	python::dict	MainModuleDict;
}


HANDLER_EXPORT bool initPlugin  (const aconnect::str2str_map& params, int handlerIndex,
								  ahttp::HttpServerSettings *globalSettings);
HANDLER_EXPORT void destroyPlugin ();
HANDLER_EXPORT bool processHandlerRequest (ahttp::HttpContext& context, int handlerIndex);


void executeScript (aconnect::string_constref scriptPath, ahttp::HttpContext& context, PyThreadState *ts);


//////////////////////////////////////////////////////////////////////////
//	
//	Windows related stuff
#if defined (WIN32)
BOOL APIENTRY DllMain( HMODULE hModule,
					  DWORD  ul_reason_for_call,
					  LPVOID lpReserved
					  )
{
	if (ul_reason_for_call == DLL_PROCESS_ATTACH)
	{
		// Don't need to be called for new threads
		DisableThreadLibraryCalls ((HMODULE) hModule);
	}
	else if (ul_reason_for_call == DLL_PROCESS_DETACH)
	{

	}
	return TRUE;
}
#endif



/* 
*	Handler initialization function - return true if initialization performed su�cessfully
*/
HANDLER_EXPORT bool initPlugin  (const aconnect::str2str_map& params, 
								  int handlerIndex,
								  ahttp::HttpServerSettings *globalSettings)
{
	assert (globalSettings);
	assert (globalSettings->logger());

	boost::mutex::scoped_lock lock(Globals::LoadMutex);
    
    if (Globals::GlobalServerSettings) {
		globalSettings->logger()->error ("Handler already initialized - python handler cannot be loaded twice");
		return false;
	}
	
	// load settings
	aconnect::str2str_map::const_iterator it = params.find(Globals::Param_UploadsDir);
	
    if (it != params.end()) {
		Globals::UploadsDirPath = it->second;
		if (!fs::exists (Globals::UploadsDirPath)) {

			try {
				fs::create_directories(Globals::UploadsDirPath);
			} catch (...) {
				globalSettings->logger()->error ("Python handler: uploads directory creation failed", 
					Globals::Param_UploadsDir.c_str() );
				
				return false;
			}
		} 
	} 

	ahttp::HttpServer::init ( globalSettings ); // should be initialized to correct work

	Globals::GlobalServerSettings = globalSettings;

	// Init Python interpreter in multithreaded mode
	Py_Initialize ();

	if ( 0 == Py_IsInitialized()) {
		globalSettings->logger()->error ("Python interpreter was not initialized correctly");
		return false;
	}

	PyEval_InitThreads();

	PyObject *serverPath = PyString_FromString (globalSettings->appLocaton().c_str());
	PySys_SetObject("executable", serverPath);
	Py_DecRef(serverPath);


	// MT support
	// save a pointer to the main PyThreadState object
	Globals::MainThreadState = PyThreadState_Get();
	// release the lock
	PyEval_ReleaseLock();

	// Register the module with the interpreter
	if (PyImport_AppendInittab("python_handler", initpython_handler) == -1)
	{
		globalSettings->logger()->error ("Failed to register 'python_handler' in the interpreter's built-in modules");
		return false;
	}

	// Retrieve the main module's namespace
	Globals::MainModule = python::import("__main__");
	Globals::MainModuleDict = python::dict (Globals::MainModule.attr("__dict__"));
	// register classes
	python::import ("python_handler");
	

	return true;
}


HANDLER_EXPORT void destroyPlugin  ()
{
	boost::mutex::scoped_lock lock(Globals::LoadMutex);

	if ( Py_IsInitialized() )
	{
		PyEval_AcquireLock();

		Py_DECREF(Globals::MainModule.ptr());
				
		PyThreadState_Swap(Globals::MainThreadState );
		
		// Py_Finalize();
	}

	Globals::MainThreadState = NULL;
	Globals::GlobalServerSettings = NULL;
}

void releasePythodThread (PyThreadState *ts) 
{
	if (ts)
	{
		// PyEval_ReleaseLock();
		// PyEval_AcquireLock();

		// clear the thread state
		// swap my thread state out of the interpreter
		ts = PyThreadState_Swap(NULL);
		
		if (ts) 
		{
			PyThreadState_Clear(ts);
			PyThreadState_Delete(ts);
		}
		
	}
}

/* 
*	Main request processing function - return false if request should
*	be processed by other handlers or by server (true: request completed)
*/
HANDLER_EXPORT bool processHandlerRequest (ahttp::HttpContext& context, 
										   int handlerIndex)
{
	using namespace ahttp;
	using namespace aconnect;
	
	if ( !util::fileExists (context.FileSystemPath.string()) ) {
		HttpServer::processError404 (context);
		return true;
	}

	if (!context.isClientConnected())
		return true;

	
	// init context
	if (!Globals::UploadsDirPath.empty())
		context.UploadsDirPath = Globals::UploadsDirPath;
	

	PyThreadState *ts = NULL;
	
	ScopedGuard<simple_callback>  guard (PyEval_ReleaseLock);
	ScopedParamGuard < void (*) (PyThreadState *), PyThreadState*> guardTs (releasePythodThread, ts);
	
	try 
	{

		// get the global lock
		PyEval_AcquireLock();
		// get a reference to the PyInterpreterState
		PyInterpreterState * mainInterpreterState = Globals::MainThreadState->interp;

		// create a thread state object for this thread
		ts = PyThreadState_New (mainInterpreterState);
		PyEval_ReleaseLock();

		if ( !ts )
			throw std::bad_alloc();
		
		PyEval_AcquireLock();
		PyThreadState_Swap(ts);

        executeScript (context.FileSystemPath.file_string(), context, ts);
		
		context.setHtmlResponse();

	} catch (python::error_already_set const &)  {
		
		try
		{
			string errDesc = loadPythonError ();
			context.Log->errorSafe(errDesc.c_str());
			
			// prepare HTML response
			errDesc = util::escapeHtml (errDesc);
			algo::replace_all (errDesc, "\n", "<br />");
			algo::replace_all (errDesc, "  ", "&nbsp;&nbsp;");
			HttpServer::processServerError(context, 500, errDesc.c_str() );
		}
		catch (...)
		{
			context.Log->error("Python handler: loading Python error failed, file: %s", 
				context.FileSystemPath.string().c_str());
		}

	} catch (std::exception const &ex)  {
		context.Log->error ("Python: exception caught (%s): %s, file: %s", 
			typeid(ex).name(), ex.what(),
			context.FileSystemPath.string().c_str());
		
		throw;
			
	} catch (...)  {
		context.Log->error ("Python: unknown exception caught, file: %s",
			context.FileSystemPath.string().c_str());
		
		HttpServer::processServerError(context, 500);
	}
	
	

	return true;
}



/**
 * Execute python code 
 */
void executeScript (aconnect::string_constref scriptPath, ahttp::HttpContext& context, PyThreadState *ts )
{
	using namespace python;
	
	HttpContextWrapper wrapper (&context);
	
	dict global (Globals::MainModuleDict.copy ());
	dict local (Globals::MainModuleDict.copy ());
	
	// prepare globals
	reference_existing_object::apply<HttpContextWrapper*>::type converter;
	handle<> wrapperHandle ( converter( &wrapper ) );
	
	local["http_context"] = wrapperHandle;
	
	/*
		Wrong way to redefine globals ('write' method will be used)
		PySys_SetObject("stdout", wrapperHandle.get());	
		PySys_SetObject("stderr", wrapperHandle.get());	
	*/

	PyObject *pyfile = PyFile_FromString (const_cast<char*>(scriptPath.c_str()), "r");
	python::handle<> file (pyfile);
	FILE* fptr = PyFile_AsFile(file.get());
	
	PyObject* result = PyRun_File (fptr,
		scriptPath.c_str(),
		Py_file_input,
		global.ptr(), 
		local.ptr());
	
	if (!result) 
		throw_error_already_set();
}

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