Note: this download requires you to have the source or binary from my
Sockets library.

Introduction

HTTP is everywhere these days. If you want to find out something, chances are you'll look for the answer on the Internet, through your browser and over HTTP. It is increasingly becoming a good idea if a lights-out server application can be monitored, and possibly administered, via HTTP. The .NET framework has good client-side support for HTTP through the System.Web namespace, and supports various clever ways of transferring data from one place to another (object remoting, Web Services), much of which is done by HTTP under the covers. But there is no simple and easy to use HTTP server available under the normal Framework; Microsoft thinks in terms of enterprises and IIS, not individual applications running a web server.

In order to provide a simple sign-up, user management, and monitoring system for my online gaming framework, which I spoke about here, I have put together a simple HTTP server that can be embedded in any .NET application, and which can be used to view the state of an application or submit material to it from within a browser.

The server supports session management (through cookies), switching folders based on the host requested (running multiple domains on the same IP), keep-alive connections, and, through the request handler which is provided, serving files from disk with substitution of pseudo-tags for dynamic content.

Use

However, the chances are you will want to perform some dynamic processing of certain URLs, support postback, or substitute particular <%pseudotags> for elements of dynamic content (for example, a navigation bar, a countern or a panel displaying the current user's private information such as private messages). In this case, you will need to inherit from SubstitutingFileReader and specify how to replace certain tags:

This trivial example will replace <%navbar> with a fixed navigation bar as specified in your code.

Sessions

The previous example raises another point: to perform useful substitutions, in most cases, you will need a session. Because HTTP is inherently stateless, you cannot save information with a connection the way you can with a typical client-server system where a connection stays alive for long periods (such as the game server). However, it is common practice in dynamic servers to circumvent this problem with a session object, which is identified by a token that can be passed back and forth between the browser and the server.

There are three common ways in which a session is kept alive over multiple requests:

By passing the session token in the URL. If you have seen websites where the address bar contains '?sessid=5b3426AF42' or similar, that is the session identifier being passed back and forth each time you move to a new page.

By setting a cookie (a piece of data stored by the browser) which the browser will send with future requests.

In certain circumstances involving form submissions, the session ID can be sent through a hidden field which will be submitted when the other fields are.

As is probably most common at present, my server uses cookies to manage the session. In terms of the application code, you need to request a session in your handler's Process method:

You can place any object in the Session object, and it will be available until that session is no longer valid. A typical use of a session is to manage authentication and login, allowing some pages to only be visible to a logged-in user. I will move on to this in the next section.

Postback

As well as serving up information, an HTTP server commonly receives information, either as a POST request (typically from a form) or via a query string appended to the URL. This information is available to your application through the Query field of HttpRequest, and you will typically want to enable posting back to a limited number of URLs. As with session management, you will need to override Process and insert code there to manage postback. Here is an example of processing a user login via postback and the HTML file that is used to generate the requests (login.html):

Note that the HTML file includes three pseudotags (navbar, loginerror, and redirect), which we have defined in the GetValue method of our handler. Obviously, you would include some form of authentication from a list of users loaded from file, or users loaded into your application already, but this example shows the basic mechanism of postback. The fields from the form end up in the request.Query hashtable.

Notice also that the example uses response.MakeRedirect() to redirect the user back to the login page in the case of a failure to log in correctly, and also to redirect the user to the target page when the login is successful. (You need to navigate to login.html?redirect=otherpage.html for this to work correctly.) MakeRedirect sends an HTTP 303, which causes the browser to request the page you pass to MakeRedirect. This allows you to separate your postback handling completely from your file reading, which could be considered a good thing; however, it is a purely stylistic choice, and you can set content in response to a POST in the ordinary way if you wish.

More on Authentication and Members' Areas

In the postback example above, the login function places an array into the Session, containing information on the user who is currently logged in. I recommend this technique, placing either an array, a Hashtable, or a custom UserInfo class in the session. Session["user"] then contains everything you need to know about the currently logged in user everywhere you might need it. For example, a simple technique to have a protected folder that you need to be logged in for (once again, place this code in your Process method):

Now, any attempt to access a URL under /members will redirect the user to a login page if they are not already logged in. (You must call RequestSession before accessing request.Session; for many applications, you will want to call RequestSession in the first line of Process so it is always available.) Of course, if you use the login code and the HTML file from earlier in this article, when you log in successfully, you are redirected back to the page you initially tried to access.

Multiple Handlers

In most cases, one handler is sufficient. However, if you prefer, it is possible to add multiple handlers (instances of IHttpHandler) to the HttpServer's Handlers list; for example, you could have a separate handler to cope with postback or protected folders, instead of adding branches to the flow of the Process method in a single handler. The last handler added will take precedence, and for each handler (working back) that returns false from Process, the previous handler will be asked to process it.

How it Works

If all you are interested in is making use of an HTTP server in your application, you can skip back to the top and click the Download link at this point. However, CodeProject being what it is, most people will be interested in some of the internals. This implementation uses my own sockets library, but similar code will be behind an HTTP server running directly from a .NET socket, or indeed a socket in another language.

The HTTP Header

Searching the Internet will quickly turn up the HTTP standard, including definitions for all the valid header fields, and a lot more detail than you will probably want to see. (This does not claim to be a complete HTTP 1.1 implementation; just enough to work.) However, an HTTP header is generally of the form:

... and is terminated by a blank line ("\r\n\r\n"). My sockets library allows for messages terminated by a text delimiter, so in the connection handler, we can set an event handler that can parse the header:

We need a Read, which reads text messages terminated by the delimiter, for the header, but we also need a ReadBytes handler to receive content, which is not terminated by a fixed delimiter and may contain any characters. The Read handler performs the relatively easy task of parsing and validating the header. Firstly, it checks that it should handle the current message:

ClientData data = (ClientData)ci.Data;
if(data.state != ClientState.Header) return;
// already done; must be some text in content, which will be handled elsewhere

... as it is possible to receive a blank line within POST content. Then, it replaces the two character "\r\n" line-end with a one-character one, and splits the header into lines. The first line contains a lot of the most important information, so that is parsed and validated first:

The URL is scanned for a question mark, and if one is found, it is split into a Page and a QueryString. Assuming the first line is valid, the remaining lines are assumed to be header fields, and are split at the colon and placed into the Header hashtable. There are three special header fields that the server looks at: Host, which is placed in request.Host and must exist; Cookie, which is parsed and placed in the Cookie hashtable; and Content-Length, which specifies how much content will follow.

Finally, the state of the connection is changed to indicate it is ready to receive content and how many bytes of header to skip, in preparation for reading the content (if any). Even if there is no content, because of the structure of my sockets library, the ClientReadBytes handler will be called and will effectively process a zero byte message.

Content

The content of the message has no fixed end delimiter, so we must hook to the binary stream of the socket, which is exposed through the ClientReadBytes event, which is called whenever data is received. This includes the header, even though we have processed that above, so we must skip over any data which was part of the header. After removing the header, what we read is simply appended to the content of the request, and if the message is complete, the query string (from the URL and from any POST content) is parsed and the request is processed.

data.headerskip is used for the next request on this connection to ensure that the content of this message is not misinterpreted as part of the header of the next, as a connection is kept alive if there is no error.

Processing

Once a request has been parsed, it is passed to a response handler to produce the result. Except in the case of an invalid (i.e., the header could not be parsed correctly) request, the server class does not process requests, although there is a default handler which is attached by default (it simply echoes information about the request back to the browser). This has two steps: first, the query string is parsed, if there is one (simply splitting on & and then on =); secondly, the request is passed to each handler in turn, starting from the latest added, until one handles it:

DoProcess performs a certain amount of administrative work before and after calling Process: first, it loads the session for this request, if there is one; and after the request is processed, it creates a new HttpRequest object for the next request to be made over this connection. Process itself works through the handlers, and when one responds to the request, it calls SendResponse (see below), keeping the connection alive if there was no error.

Responding

The final stage is sending a response once the application has determined what the content should be. This involves adding a valid HTTP header to the message and then sending the content, which may either be binary or text (which is sent as UTF-8).

Session Management

One useful feature of this server is session management. Most of the code to manage sessions has in fact already been shown; in DoProcess, the cookie _sessid is checked for and a session is loaded if it exists, and in SendResponse, the cookie is set if there is a session. There are two other methods related to sessions: RequestSession, the public method used to obtain a valid session; and CleanUpSessions, which is called whenever any request is processed, and which removes any sessions that have expired.

License

Share

About the Author

I'm a recent graduate (MSci) from the University of Cambridge, no longer studying Geology. Programming is a hobby so I get to write all the cool things and not all the boring things . However I now have a job in which I have to do a bit of work with a computer too.

As a simple test I tried to serve static HTML files using the example in the article (using port 8741 instead of 80). It seems the last 256 bytes in a file are not returned to the browser.

I downloaded the source files, for the HTTP server and the sockets library, here from The Code Project. They were compiled in Visual Studio, with a separate project for the 2 files for consumption of the resulting DLL by the main program, in another project (one solution with 2 projects).

Except for adding required references and imports the compiling mostly went without a glitch (one of the source files contained a line that is normally in file "AssemblyInfo.cs" that Visual Studio generates from a normal template for a class library).

I tried this:

* Running under Visual Studio and completely separate
* Running on two different computers with two different versions of Windows of two different bitnesses (Windows 7 32-bit and Windows XP 64-bit)
* Running as administrator and not administrator
* Using two different browsers (Firefox and Internet Explorer)

It all gave the same result, so I think the problem is with the library.

I used a barebones syntactically-valid HTML file with only paragraphs of text (<p></p>).

I tried it on three files. One was 90,432 bytes long. The 256 could be a coincidence, so I tried it on a file 7 bytes longer. It also had 256 bytes missing when served by this HTTP server.

For reproduction of the error I have made the two files available for download (a browser may reformat it if displayed and saved - but "Save Link As" or similar in a browser works, e.g. from <http://pmortensen.eu/temp2/twoFiles.html>):

http://pmortensen.eu/temp2/xyz9.html (90,432 bytes)

http://pmortensen.eu/temp2/xyz10.html (90,439 bytes)

(Note that the last two paragraphs in both files are a little different to uniquely identify how many bytes are not served.)

As user "Member 8979759", I also noticed that serving files over a few kilobytes is shockingly slow (unacceptably slow if more than 100 KB).

As an example, serving a static file of 44 KB took 37 seconds. I tried to insert end-line-lines for every 32 bytes in the hexadecimal output, thinking it was the very long line that caused Visual Studio to be slow. But this change only improved it to 28 seconds.

The problem is the amount of data returned from case "ParameterType.Byte" in function FormatParameter() in class ByteBuilder, file Sockets.cs.

Another way to fix the problem is returning less. I replaced "return sb.ToString();" with "return (byteCount.ToString() + " bytes (bytes not formatted as hexadecimal here)");" so it just prints a summary of how many bytes there were instead of printing all of the bytes. I presume this will not have side-effects as turning it completely off by means of symbol "DEBUG" also works.

A complete example would be useful, because there are a number of assumptions and spelling differences in the text where a number of items that are important have been glossed over. This means that it can only really be used by someone who already knows enough to implement it themselves anyway.

What I wanted to do was create a simple site showing status information requiring a login. I'll get there but its not a pleasurable experience, and an example would be most useful. Still, if it worked out the box I probably wouldn't have understood as much.

Hi Jaymz, thanks for the comment. I think you're right and I should probably include a fully working example of some kind of members-only site in the download. It's Christmas Eve so it won't get done immediately but I'll try to get something up there in January.

Hope you had a good Christmas? I'd like to pick this one up again if possible. I spent quite a bit of effort trying to get this working, but I'm seeing a lot of errors and its difficult to debug. How about a skeleton showing it working in context with a simple log in, then a status page (the substituting bit looks fine) but I cannot relate the session to the original login. I suspect that you used this code in a specific application?

As it says in the introduction, I use it in my game server to provide a remote status/admin capability. You could use it anywhere where you want to provide HTTP (i.e. browser) access to your application without having to make a plugin for an existing web server.

Uploads are usually sent as POST content. You can retrieve this in the server by looking at Request.Content. If you MIME or Base64 encode it then you'll have to decode it manually, though it looks like you are sending 8 bit binary in this example so that isn't necessary.