Introduction
Originally, I was intrigued with what it would take to write a simple webserver. But as I progressed, I realized that a tiny web server could be quite useful for a number of applications that need to serve specialized web pages and where the overhead of writing an ASP.NET application is not warranted (or where it is not possible to host ASP.NET).
A good example is a news aggregator, which serves a single page containing the current news feeds. This is included as an example in this project.
Update (July 2013): As has been requested, I added handling of the POST
command.
Using the Code
The core of the application is the class TinyServer
. This class provides a simple web server that only supports GET
requests (no forms) and serves web pages from a directory.
To run the sample webserver, you need to build the WebServer
project and configure its settings:.
WebRoot : "E:\src\DotNet\WebServer\root" <!-- location of the web pages to serve -->
DefaultPage :"default.html" <!-- name of the default page -->
TemplatePath: "E:\src\DotNet\WebServer\html" <!-- location of special templates -->
Port : 81" <!-- Port to server on -->
LogFile : "" <!-- filepath, set to "" for console logging -->
LogLevel : "All" <!--All, Warning, Error, None -->
Once the WebServer
application starts, it instantiates TinyServer
and calls Run()
. This spins off the server in a separate thread. Calling Stop()
terminates the thread.
Building Your Own WebServer
Most likely, you would want to build your own version of this webserver. You need to subclass TinyServer
and then override the necessary functions. The most important to override is the method doGet()
. In this method, you can interrogate the GET
command and send back anything that is necessary.
This is the default implementation to handle a GET
command:
protected virtual void doGet(string argument)
{
string url = getUrl(argument);
if (url.StartsWith("/"))
url = url.Substring(1);
if (url.Length == 0)
url = defaultPageName;
string path = Path.Combine(webRootPath, url);
if (File.Exists(path))
{
sendOk();
sendfile(path);
}
else
sendError(404, "File Not Found");
}
To handle a POST
command, there is a doPost
function that you can override. Currently, it does very little:
protected virtual void doPost(string argument, string Content)
{
log(LogKind.Informational, "Post Data: '{0}'", Content);
sendOk();
}
As you can see, the content of the post is available as a string
.
To implement your version, there are a number of utility functions at hand:
string getUrl(string argument)
takes the command parameter of doGet
and extracts the URL string [] urlArgs
returns a list of arguments that succeeded the URL sendOK()
sends the OK header; this is necessary before you send any HTML sendError(int errornr, string errorMsg)
sends an error instead of the OK sendString(string)
sends a message sendFile(path)
sends a whole file sendTemplate (templateName)
sends a file in the template directory
The RssAggregator Sample Application
To demonstrate this ability, I have written a simple news aggregator that regularly downloads RSS feeds from the sources.
The RssAggregator
does two things:
- Downloads and keeps up to date a list of selected RSS feeds
- Runs a web server that returns a web page containing the feed detail
The first part uses the RssReader
class created by smallguy78. It runs in its own thread and will download feeds once the current copy is older than one hour.
The second part is implemented by a subclass of TinyServer
called AggServer
. AggServer
only ever returns one page that contains the newsfeeds abstracts and links to the articles. So doGet()
is pretty dumb:
protected override void doGet(string argument)
{
this.sendOk();
this.sendString(writeLinkPage());
}
The smarts to create the webpage is in the method writeLinkPage()
which in turn relies on the helper function RssReader.CreateHtml()
. The whole example (excluding RssReader
) just takes 80 lines of code.
Points of Interest
Acknowledgements to smallguy78 whose RssReader
code I used. You can find more about it in this RSS Reader article.