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
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:
aconnect
static library: contains multithreaded TCP server
implementation, file logger, and great amount of utility functionality - TCP
socket control, string processing algorithms, date/time functions, cryptography;ahttplib
static library: HttpServer
definition
(see details in first article) and
all HTTP requests parsing/processing functionality, server settings loading code;ahttpserver
: simple HTTP server application.To extend server features set of plugins was developed:
handler_aspnet
- ASP.NET application support (available only under Windows). Common architecture of
this plugin was copied from .NET Cassini server;handler_isapi
- IIS ISAPI extensions wrapper - using this wrapper ahttpserver can utilize already developed extensions
to support different script engines. This handler works correctly with PHP 4 and 5, I have tried to use ASP and ASP.NET ISAPI extensions
but both of them uses undocumented features of IIS and cannot be loaded into ahttpserver;handler_python
- Python scripts support, works differently from general approach to server-side Python -
this module executes scripts directly;module_authbasic
- Basic authentication support - two types of auth. providers available. Server provider
authenticates users against list loaded from file and system provider (now works only under Windows) authenticates users
against OS.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.
<?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:
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.ahttp
library - it is plugin that can perform processing of defined file types, like ISAPI extension in IIS
or HttpHandler in ASP.NET.
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).
mapPath
method.
Default value - 'false'.// 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.
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:
templatecannot 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.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; };
<handlers> <register name="handler_aspnet" ext="*"/> <unregister name="handler_aspnet" ext=".gif; .js; .css; .jpg; .png"/> </handlers>