![]() |
Languages »
C / C++ Language »
General
Beginner
License: The Code Project Open License (CPOL)
Small and Reliable C++ HTTP Server with Complete ASP.NET SupportBy Artem KustikovThis article describes results of ahttpserver evolution - implementation of ASP.NET handler and many architecture improvements |
C++, Windows, .NET, Linux
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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 applicationThe 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 ahttpserverhandler_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.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.
<?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"
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-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>
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-countpooling-enabledpooling-enabled = 'false') or multithreadedworker-life-timecommand-portahttpserver to open additional listening port to receive server control commands ('start', 'stop', 'reload')keep-alive-enabledserver-socket-timeoutresponse-buffer-sizemax-chunk-sizedirectory-config-filemessages-fileuploads-dirlocalesetlocale (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.logmime-typesContent-Type' header for this file. Types can be defined directly in this element's body or loaded from external file. handlersahttp library - it is a plugin that can perform processing of defined file types, like ISAPI extension in IIS or HttpHandler in ASP.NET. modulesahttp 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).
namepathrelative-pathvirtual-pathmax-request-sizeenable-parent-path-accessmapPath method. Default value - 'false'browsing-enabledheader-template", "parent-directory-template", "directory-template", "virtual-directory-template", "file-template" and "footer-template" used to format directory content HTML handlersahttp 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. // 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 my best to write all simple code.
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
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.
aconnect library:
ICacheable<T>) - very challenging task - I am planning to create something like .NET Cache in pure C++.ahttp library and plugins:
handler_isapi - multiple mappings)HttpServer (using aconnect::Cache)boost::iostreams)static content(::FreeLibrary call)aconnect: Optimized worker threads pooling mechanismahttp: Server messages loading from XML (localization)ahttp: Implemented handlers unloading mechanism (destroyHandlers)ahttp: Implemented stable multithreaded Python handler versionahttp: 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 HttpContextahttp: 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).aconnect: Implemented background file logger: collect messages to write and write them in background (avoid locking on file writing)ahttp: Redeveloped handlers registration (ahttp: Implemented case-insensitive request/response headers save/load (useful for handlers)ahttp: [Windows] Implemented ASP.NET handler (Managed C++)ahttp: Developed multiple extensions mapping for handler in register@ext and in handler@default-ext (list of ext linked to named handler), now ASP.NET MVC can be setup by following settings in <handlers>
<register name="handler_aspnet" ext="*"/>
<unregister name="handler_aspnet" ext=".gif; .js; .css; .jpg; .png"/>
</handlers>
ahttp: Added max-request-size setting for directory (deny requests with larger content-length, send 413 HTPP error)ahttp: Implemented "If-Modified-Since" header supportahttp: Added support of "Accept-Ranges" header used for partial content downloadahttp: Developed server modules support (like HttpModule in .NET: onRequestBegin, onRequestMapHandler, onResponsePreSendHeaders, onResponsePreSendContent, onResponseEnd)ahttp: Basic authentication (module)
General
News
Question
Answer
Joke
Rant
Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads.
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 5 Feb 2009 Editor: Deeksha Shenoy |
Copyright 2009 by Artem Kustikov Everything else Copyright © CodeProject, 1999-2010 Web17 | Advertise on the Code Project |