Title:       Small and reliable C++ HTTP server with complete ASP.NET support
Author:      Artem Kustikov 
Email:       kustikoff@tut.by
Member ID:   2332143
Language:    C++
Platform:    Windows, Ubuntu Linux
Technology:  STL, Boost.Thread, Boost.Python, TinyXML, Managed C++
Level:       Beginner, Intermediate
Description: This article describes results of ahttpserver evolution - implementation of ASP.NET handler and many architecture improvements
Section      C/C++ Language, .NET framework
SubSection   General
License:     CPOL

Introduction

This article describes changes in ahttp library made from first release. I have decided to upload new article because server code was greatly modified and at present it is almost production ready project.

Current server version can be easily used as an IIS replacement in ASP.NET applications development. Server supported wildcard ('.*') mapping with exclude exception list maintaning - this feature can be used to setup ASP.NET MVC application (see example in attached demo).

After first ahttp version sharing (about year ago) I have continued working on this project, main goal of this developments stayed unchanged - investigate and use latest features available in C++, apply different known design practices to create stable and extendable application architecture. This project is developed at free time and not related to my direct work - in office life mostly I work on web-applications (ASP.NET, JavaScript, jQuery, last time I participate in project based on ASP.MVC).

Currently ahttp project contains three main parts:

Server is still support Windows and Linux platforms (tested on Ubuntu only).

To extend server features set of plugins was developed:

Using the Code

Server/library architecture

All HTTP server kernel code is located in ahttp library to make possible embedding of the server into any existing architecture or use only necessary parts of server code, like HTTP requests parsing. Using this library provides developer ability to create customizable web-server to process specific HTTP requests - SOAP requests, files downloading and so on.

Complete standalone HTTP server application (ahttpserver) included in project sources has only about 15 Kb of sources (except libraries code of course). Thus using of this library can greatly decrease developing/prototyping efforts at server-side project estimation. Even it will not be decided to use this library as service base one can use provided sources parts to include in own project.

To get ahttp::HttpServer working one need have filled HttpServerSettings instance - even for simple server there are many settings that can be setup. As a result the preferred place to store these settings is a XML file which can be updated by hands and loaded quickly.

Server settings file

<?xml version="1.0" encoding="utf-8"?>
<settings>
    <server
	    version = "ahttp/0.17"
	    port="5555"
	    ip-address="0.0.0.0"
	    workers-count="50"
	    pooling-enabled="true"
	    command-port="5556"
	    root="root"

	    keep-alive-enabled = "true"
	    keep-alive-timeout = "10"
	    server-socket-timeout = "900"
	    command-socket-timeout = "30"
				
	    response-buffer-size = "8194304"
	    max-chunk-size = "512144"

	    directory-config-file = "directory.config"
	    messages-file = "messages.config"
		
	    uploads-dir = "c:\\temp\\ahttp"
	    locale=".1251"
	    >


	    <!-- log-level: "Debug", "Info", "Warning", "Error", "Critical" - if none of them - then debug -->
	    <log log-level="info" max-file-size="4194304">

		    <!-- {app-path} - path to directory where application is located (with trailing slash),
			     {timestamp} - generated timestamp -->
		    <path>{app-path}log\server_{timestamp}.log</path>
	    </log>

	    <mime-types file="{app-path}mime-types.config" />

	    <!-- All handlers must be registered there, concrete 
		    assignments will be defined in <directory> elements -->
	    <handlers>
		    <register name="handler_python" default-ext=".py; .pyhtml">
			    <path>{app-path}handler_python-d.dll</path>
			    <!-- parameter name="uploads-dir">c:\temp\handler_python\</parameter -->
		    </register>
		    <register name="handler_php" default-ext=".php">
			    <path>{app-path}handler_isapi-d.dll</path>
			    <parameter name="engine">c:\PHP\php5isapi.dll</parameter>
			    <parameter name="update-path">c:\PHP\</parameter>
			    <parameter name="free-library">false</parameter>
			    <parameter name="check-file-exists">true</parameter>
		    </register>
		    <register name="handler_aspnet" default-ext=".aspx; .ashx; .asmx; .axd">
			    <path>{app-path}handler_aspnet-d.dll</path>
			    <parameter name="init-root">false</parameter>
			    <!-- parameter name="load-applications">mvc; books</parameter -->
		    </register>
	    </handlers>
	    
        <!-- All modules must be registered there, concrete 
        assignments will be defined in <directory> elements.
        'global' attribute defines that this module will be automatically applied to root directory.-->

        <modules>
            <register name="global_basic_auth" global="true">
	            <path>{app-path}module_authbasic-d.dll</path>
	            <parameter name="realm">Protected data</parameter>
	            <parameter name="provider">system</parameter>
	            <parameter name="default-domain">ES</parameter>
            </register>
        </modules>


    </server>

    <!-- virtual-path for root: "/"
			    'charset' - will be used when FS content is shown   
			    default 'max-request-size': 2097152 bytes -->
    <directory name="root"
		    browsing-enabled="true"
		    charset="Windows-1251"
		    max-request-size="2097152"
		    enable-parent-path-access="true">

	    <path>d:\work\web\</path>

	    <default-documents>
		    <add>index.html</add>
		    <add>index.htm</add>
		    <add>main.html</add>
            <add>Default.aspx</add>
	    </default-documents>

	    <!-- ext="*" - will be applied to all requests -->
	    <!-- ext="." - will be applied to directory/file without extension -->
	    <handlers>
		    <add name="handler_python"/>
		    <add name="handler_php" />
		    <add name="handler_aspnet"/>
       </handlers>

	    <!-- Record attributes: 
			    {name} - name of item, 
			    {size} - size of item in kb,
			    {url} - url to open item
			    {time} - last modify dat/time of item,
			    {page-url} - url to current page
			    {parent-url} - url to parent directory
			    {files-count} - files count in current directory
			    {directories-count} - sub-directories count in current directory
			    {errors-count} - reading errors count 
			    {tab} - will be replaced with '\t'
	    -->
	    	<header-template>
		<pre>{eol}
		<b>Directory: <i>{page-url}</i></b>{eol}{eol}
	</header-template>

	<parent-directory-template >
		<a href="{parent-url}">[parent directory]</a>{eol}{eol}
	</parent-directory-template>

	<directory-template>
		{time}{tab}{tab}directory{tab}{tab}<a href="{url}">{name}</a>{eol}
    </directory-template>

	<virtual-directory-template >
		{time}{tab}{tab}  virtual{tab}{tab}<a href="{url}">{name}</a>{eol}
    </virtual-directory-template>

	<file-template >
        {time}{tab}{size}{tab}{tab}<a href="{url}">{name}</a>{eol}
    </file-template>

	<footer-template>
        {eol}
		Files: {files-count}{eol}
		Directories: {directories-count}{eol}
		Reading errors: {errors-count}{eol}
		</pre>
	</footer-template>
    </directory>

    <directory name="server_data"
		    parent="root">
	    <virtual-path>server_data</virtual-path>
	    <path>{app-path}web</path>
    </directory>
    
    <directory name="mvc"
	       parent="root">
	    <handlers>
		    <add name="handler_aspnet" ext="*"/>
		    <remove name="handler_aspnet" ext=".gif; .js; .css; .jpg; .png"/>
	    </handlers>
	    <virtual-path>mvc</virtual-path>
	    <path>d:\work\Visual Studio 2008\Projects\OReilly-.NET3.5\MVCApplication\</path>
    </directory>
</settings>
    

First section of settings file - server defines HTTP server startup/runtime behavior: server's port (port attribute), IP address to bind on it (now only IPv4 is supported). Other parameters:

workers-count
Maximal worker threads count in thread's pool.
pooling-enabled
Defines server working mode - single threaded (pooling-enabled = 'false') or multithreaded.
command-port
Used in ahttpserver to open additional listening port to receive server control commands ('start', 'stop', 'reload').
keep-alive-enabled
Setup HTTP Keep-Alive mode.
server-socket-timeout
HTTP server socket read/write timeout (in seconds).
response-buffer-size
HTTP response buffer size (in bytes).
max-chunk-size
Maximal chunk size in chunked response mode (defined in bytes).
directory-config-file
Name of inplace file, located in server virtual directory and used to load default document list, plugins registration and server URL mappings setup (see sample in sources package).
messages-file
Server messages localization file.
uploads-dir
Global uploads directory to store posted files content.
locale
Important setting - call setlocale (LC_CTYPE, localeStr.c_str()) will be performed at server startup when this attribute is not empty. Locale setup can be used to force mbstowcs work correctly, for example I have setup ".1251" locale to correctly transform file names defined in Windows-1251 encoding to Unicode.
log
This element defines global file logger setup, well known logging levels set is used.
mime-types
This element defines correspondence between file extension and MIME type send in 'Content-Type' header for this file. Types can be defined directly in this element's body or loaded from external file.
handlers
This element should contain all planned to use handlers registration. Each handler registration defines path to DLL/SO file to load and set of parameters that will be sent to handler initialization method. Handler in ahttp library - it is plugin that can perform processing of defined file types, like ISAPI extension in IIS or HttpHandler in ASP.NET.
modules
This element should contain all planned to use modules registration. Each module registration defines path to DLL/SO file to load and set of parameters that will be sent to module initialization method. Module in ahttp library - it is plugin that can contain set of callbacks which will be used at defined HTTP request processing end-points like HttpModule in ASP.NET. At present following events for module are supported: ModuleCallbackOnRequestBegin, ModuleCallbackOnRequestResolve, ModuleCallbackOnRequestMapHandler, ModuleCallbackOnResponsePreSendHeaders, ModuleCallbackOnResponsePreSendContent, ModuleCallbackOnResponseEnd.

Virtual directory setup - directory element. Each virtual directory can be defined by absolute FS path ('path' attribute) or by relative path from parent's directory ('relative-path' attribute).

name
Mandatory attribute - used to build directories tree from server's root.
path
Used to setup virtual directory absolute FS path.
relative-path
Used to setup virtual directory relative FS path.
virtual-path
Defines virtual path of directory.
max-request-size
Optional attribute - defines maximal HTTP request size that can be processed by server. Default value - 2097152 bytes.
enable-parent-path-access
Optional attribute - used to deny access to parent directory from mapPath method. Default value - 'false'.
browsing-enabled
Enables directory browsing mode. "header-template", "parent-directory-template", "directory-template", "virtual-directory-template", "file-template" and "footer-template" used to format directory content HTML.
handlers
This element defines ahttp handlers setup for current directory, can contain following elements: 'add', 'remove', 'clear', 'register'. All handlers registered for parent directory are applied to all children by default.

Complete sample code of very simple server

    
    // globals
    namespace Global
    {
	    aconnect::string settingsFilePath;
        ahttp::HttpServerSettings globalSettings;
	    
	    aconnect::BackgroundFileLogger logger;
	    aconnect::Server httpServer;
    }
    
    void processException (aconnect::string_constptr message, int exitCode) {
	    std::cerr << "Unrecorable error caught: " << message << std::endl;
	    exit (exitCode);
    }
    
    int main (int argc, char* args[]) 
    {
        using namespace aconnect;
	    namespace fs = boost::filesystem;

        if (argc < 2) {
            std::cerr << "Usage: " << args[0] <<  " " << std::endl;
        }
        
        Global::settingsFilePath = args[1];
	    string appPath = aconnect::util::getAppLocation (args[0]);
    	
        try 
	    {
		    Global::globalSettings.setAppLocaton ( fs::path(Global::appPath).remove_leaf().directory_string().c_str() );
		    Global::globalSettings.load ( Global::settingsFilePath.c_str() );
    		
	    } catch (std::exception &ex) {
		    processException (ex.what(), 1);
	    } catch (...) {
		    processException ("Unknown exception caught at settings loading", 1);
	    }
    	
	    try 
	    {
		    // create global logger
		    string logFileTemplate = Global::globalSettings.logFileTemplate();
		    Global::globalSettings.updateAppLocationInPath (logFileTemplate);
		    fs::path logFilesDir = fs::path (logFileTemplate, fs::native).branch_path();
		    if (!fs::exists (logFilesDir))
			    fs::create_directories(logFilesDir);

		    Global::logger.init (Global::globalSettings.logLevel(), logFileTemplate.c_str(), Global::globalSettings.maxLogFileSize());

	    } catch (std::exception &ex) {
		    processException (ex.what(), 2);
	    } catch (...) {
		    processException ("Unknown exception caught at logger creation", 2);
	    }

	    Global::globalSettings.setLogger ( &Global::logger);
	    // init ahttp library
        ahttp::HttpServer::init ( &Global::globalSettings);

	    try 
	    {
    	
		    Global::globalSettings.initPlugins(ahttp::PluginModule);
		    Global::globalSettings.initPlugins(ahttp::PluginHandler);

		    Global::httpServer.setLog ( &Global::logger);
		    Global::httpServer.init (Global::globalSettings.port(), 
			    ahttp::HttpServer::processConnection, 
			    Global::globalSettings.serverSettings());
        	
		    Global::httpServer.start (true);

	    } catch (std::exception &ex) {
		    processException (ex.what(), 3);
	    } catch (...) {
		    processException ("Unknown exception caught at server startup", 3);
	    }
    	
        return 0;
    }
    

See more details in library code - I tried to write all code as simple as possible.

Points of Interest

At working on this project I realized that C++ is still the best variant for high-load server-side services. The strongly typed language that provides ability to write short but fast and powerful constructions like this:

    template  
    class ScopedMemberPointerGuard {
    public:
        ScopedMemberPointerGuard (T* obj, F T::* member, F initialValue ) : 
            _obj (obj), _member (member) {
                _obj->*_member = initialValue;
        }

        ~ScopedMemberPointerGuard () { 
            _obj->*_member = 0; 
        }
        
    private:
        T* _obj;
        F T::* _member;
    };
    
cannot be forgotten by developers. Working on this project I have got great experience in ISAPI extensions internal architecture, ASP.NET HTTP runtime programming in native environment - all these skills are not trivial programming tasks and can be effectively used in professional work.

Planned Improvements

aconnect library:
ahttp library and plugins:

Known compatibility issues

  1. Using of c:\PHP\php5isapi.dll through ISAPI handler (tested with PHP 5.2.5 and PHP 4.3.10) can be be the cause of "Access violation" exception at server stopping (::FreeLibrary call)
  2. ASP.NET handler tested only with .NET framework v2.0.50727 (Microsoft .NET Framework 3.5 SP1 installed)

Version History

Ver. 0.15

Ver. 0.16

Ver. 0.17

Ver. 0.18

Ver. 0.19