Introduction
This article describes changes in ahttp
library made from first release. I have decided to upload a new article because the server code was greatly modified and at present it is almost a production ready project.
The current server version can be easily used as an IIS replacement in ASP.NET applications development. Server supported wildcard ('.*') mapping with exclude exception list maintaining - 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. The main goal of this development 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 in my free time and is not related to my direct work - in office life mostly I work on web-applications (ASP.NET, JavaScript, jQuery, last time I participated in a project based on ASP.MVC).
Currently, the ahttp
project contains three main parts:
aconnect
static library: contains multithreaded TCP server implementation, file logger, and a great amount of utility functionality - TCP socket control, string processing algorithms, date/time functions, cryptographyahttplib
static library: HttpServer
definition (see details in first article) and all HTTP requests parsing/processing functionality, server settings loading codeahttpserver
: simple HTTP server application
The server still supports Windows and Linux platforms (tested on Ubuntu only).
To extend server features, a 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 serverhandler_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 use 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 directlymodule_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.
Using the Code
Server/Library Architecture
All HTTP server kernel code is located in the 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 a developer the 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 if it will be decided not to use this library as service base, one can use the provided source parts to include in their 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 an XML file which can be updated by hand and loaded quickly.
Server Settings File
="1.0"="utf-8"
<settings>
<server
version = "ahttp/0.17"
port="5555"
ip-address="0.0.0.0"
workers-count="50"
pooling-enabled="true"
worker-life-time="60"
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 log-level="info" max-file-size="4194304">
<path>{app-path}log\server_{timestamp}.log</path>
</log>
<mime-types file="{app-path}mime-types.config" />
<handlers>
<register name="handler_python" default-ext=".py; .pyhtml">
<path>{app-path}handler_python-d.dll</path>
</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>
</register>
</handlers>
<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>
<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>
<handlers>
<add name="handler_python"/>
<add name="handler_php" />
<add name="handler_aspnet"/>
</handlers>
<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>
The 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
worker-life-time
- Worker thread release timeout in seconds
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
to 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 a 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 a plugin that can contain a set of callbacks which will be used at defined HTTP request processing end-points like HttpModule
in ASP.NET. At present, the 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 a Very Simple Server
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] << " <server-config-file />" << 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
{
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);
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 my best to write all simple code.
Points of Interest
While working on this project, I realized that C++ is still the best variant for high-load server-side services. The strongly typed language provides the ability to write short but fast and powerful constructions like this...
template <typename />
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:
- UDP server support
- TCP/UDP client
- Cache class (stores
ICacheable<T>
) - very challenging task - I am planning to create something like .NET Cache in pure C++.
ahttp
library and plugins:
- Implement CGI/FastCGI handler (like
handler_isapi
- multiple mappings) - Implement found-targets cache in
HttpServer
(using aconnect::Cache
) - "gzip/deflate" content encoding support, module (with zlib or
boost::iostreams
) - HTTP client
- In-memory cache module for
static
content - Introduce Django support for Python handler
Known Compatibility Issues
- Using of c:\PHP\php5isapi.dll through ISAPI handler (tested with PHP 5.2.5 and PHP 4.3.10) can be the cause of "Access violation" exception at server stopping
(::FreeLibrary
call) - ASP.NET handler tested only with .NET Framework v2.0.50727 (Microsoft .NET Framework 3.5 SP1 installed)
Version History
Ver. 0.15
aconnect
: Optimized worker threads pooling mechanismahttp
: Server messages loading from XML (localization)ahttp
: Implemented handlers unloading mechanism (destroyHandlers
)ahttp
: Implemented stable multithreaded Python handler version- Many code refactorings performed (class members, namespaces, error handling)
- Windows: Visual Solution solution converted to Visual Studio 2008
Ver. 0.16
ahttp
: Defined handler ID and process request by correct DLL - used in ISAPI handler, can be used in other handlers which can be linked to several extensionsahttp
: VirtualPath
renamed to InitialVirtualPath
, MappedVirtualPath
to VirtualPath
to make them consistentahttp
: Added using aconnect::string
and related types into ahttp
namespaceahttp
: [Windows] ISAPI extensions wrapper (handler), tested with PHP ISAPI extension.ahttp
: Implemented SERVER VARIABLES collection support in HttpContext
ahttp
: Implemented safe handlers unloading (::FreeLibrary
/dlclose
)aconnect
: Implemented ability to start server on defined IP, loaded from config (default value: 0.0.0.0 - INETADDR_ANY
).
Ver. 0.17
Ver. 0.18
ahttp
: Implemented "If-Modified-Since
" header supportahttp
: Added support of "Accept-Ranges
" header used for partial content download
Ver. 0.19
ahttp
: Developed server modules support (like HttpModule
in .NET: onRequestBegin
, onRequestMapHandler
, onResponsePreSendHeaders
, onResponsePreSendContent
, onResponseEnd
)ahttp
: Basic authentication (module)