Click here to Skip to main content
Click here to Skip to main content

Simple HTTP Server in C#

By , 23 Mar 2013
 

Introduction

This article covers a simple HTTP server class which you may incorporate into your own projects, or review to learn more about the HTTP protocol.

Background 

High performance web services are often hosted in rock solid webservices like IIS, Apache, or Tomcat. However, HTML is such a flexible UI language, that it can be useful to serve an HTML UI out of practically any application or backend server. In these situations, the overhead and configuration complexity of an external webserver is seldom worth the trouble. What's needed is a simple HTTP class which can be easily embedded to service simple web requests. This class meets that need.

Using the Code

First let's review how to use the class, and then we'll dig into some of the details of how it operates. We begin by subclassing HttpServer and providing implementations for the two abstract methods handleGETRequest and handlePOSTRequest...

public class MyHttpServer : HttpServer {
    public MyHttpServer(int port)
        : base(port) {
    }
    public override void handleGETRequest(HttpProcessor p) {
        Console.WriteLine("request: {0}", p.http_url);
        p.writeSuccess();
        p.outputStream.WriteLine("<html><body><h1>test server</h1>");
        p.outputStream.WriteLine("Current Time: " + DateTime.Now.ToString());
        p.outputStream.WriteLine("url : {0}", p.http_url);
        
        p.outputStream.WriteLine("<form method=post action=/form>");
        p.outputStream.WriteLine("<input type=text name=foo value=foovalue>");
        p.outputStream.WriteLine("<input type=submit name=bar value=barvalue>");
        p.outputStream.WriteLine("</form>");
    }
    
    public override void handlePOSTRequest(HttpProcessor p, StreamReader inputData) {
        Console.WriteLine("POST request: {0}", p.http_url);
        string data = inputData.ReadToEnd();
        
        p.outputStream.WriteLine("<html><body><h1>test server</h1>");
        p.outputStream.WriteLine("<a href=/test>return</a><p>");
        p.outputStream.WriteLine("postbody: <pre>{0}</pre>", data);
    }
}

Once a simple request processor is provided, one must instantiate the server on a port, and start a thread for the main server listener.

 HttpServer httpServer = new MyHttpServer(8080);
 Thread thread = new Thread(new ThreadStart(httpServer.listen));
 thread.Start(); 

If you compile and run the sample project, you should be able to point a web-browser of choice at http://localhost:8080 to see the above simple HTML pages rendered. Let's take a brief look at what's going on under the hood.

This simple webserver is broken into two components. The HttpServer class opens a TcpListener on the incoming port, and sits in a loop handling incoming TCP connect requests using AcceptTcpClient(). This is the first step of handling an incoming TCP connection. The incoming request arrived on our "well known port", and this accept process creates a fresh port-pair for server to communicate with this client on. That fresh port-pair is our TcpClient session. This keeps our main accept port free to accept new connections. As you can see in the code below, each time the listener returns a new TcpClient, HttpServer creates a new HttpProcessor and starts a new thread for it to operate in. This class also contains the abstract methods our subclass must implement in order to produce a response.

public abstract class HttpServer {

    protected int port;
    TcpListener listener;
    bool is_active = true;
   
    public HttpServer(int port) {
        this.port = port;
    }
    
    public void listen() {
        listener = new TcpListener(port);
        listener.Start();
        while (is_active) {                
            TcpClient s = listener.AcceptTcpClient();
            HttpProcessor processor = new HttpProcessor(s, this);
            Thread thread = new Thread(new ThreadStart(processor.process));
            thread.Start();
            Thread.Sleep(1);
        }
    }
    
    public abstract void handleGETRequest(HttpProcessor p);
    public abstract void handlePOSTRequest(HttpProcessor p, StreamReader inputData);
} 

At this point, the new client-server TCP connection is handed off to the HttpProcessor in its own thread. The HttpProcessor's job is to properly parse the HTTP headers, and hand control to the proper abstract method handler implementation. Let's look at just a few small parts of the HTTP header processing. The first line of an HTTP Request resembles the following:

GET /myurl HTTP/1.0 

After setting up the input and output stream in process(), our HttpProcessor calls parseRequest(), where the above HTTP request line is received and parsed.

public void parseRequest() {
    String request = inputStream.ReadLine();
    string[] tokens = request.Split(' ');
    if (tokens.Length != 3) {
        throw new Exception("invalid http request line");
    }
    http_method = tokens[0].ToUpper();
    http_url = tokens[1];
    http_protocol_versionstring = tokens[2];

    Console.WriteLine("starting: " + request);
} 

The HTTP request line is always three parts, so we simply use a string.Split() call to separate it into three pieces. The next step is to receive and parse the HTTP headers from the client. Each header-line includes a type of the form KEY:Value. An empty line signifies the end of the HTTP headers. Our code to readHeaders is the following:

public void readHeaders() {
    Console.WriteLine("readHeaders()");
    String line;
    while ((line = inputStream.ReadLine()) != null) {
        if (line.Equals("")) {
            Console.WriteLine("got headers");
            return;
        }
                
        int separator = line.IndexOf(':');
        if (separator == -1) {
            throw new Exception("invalid http header line: " + line);
        }
        String name = line.Substring(0, separator);
        int pos = separator + 1;
        while ((pos < line.Length) && (line[pos] == ' ')) {
            pos++; // strip any spaces
        }
                    
        string value = line.Substring(pos, line.Length - pos);
        Console.WriteLine("header: {0}:{1}",name,value);
        httpHeaders[name] = value;
    }
}

For each line, we look for the colon (Smile | :) separator, grabbing the string before as a name, and the string after as a value. When we reach an empty header-line, we return because we have received all headers.

At this point, we know enough to handle our simple GET or POST, so we dispatch to the proper handler. In the case of a post, there is some trickiness to deal with in accepting the post data. One of the request headers includes the content-length of the post data. While we wish to let our subclass's handlePOSTRequest actually deal with the post data, we need to only allow them to request content-length bytes off the stream, otherwise they will be stuck blocking on the input stream waiting for data which will never arrive. In this simple server, we handle this situation with the dirty but effective strategy of reading all the post data into a MemoryStream before sending this data to the POST handler. This is not ideal for a number of reasons. First, the post data may be large. In fact it may be a file upload, in which case buffering it into memory may not be efficient or even possible. Ideally, we would create some type of stream-imitator that could be setup to limit itself to content-length bytes, but otherwise act as a normal stream. This would allow the POST handler to pull data directly off the stream without the overhead of buffering in memory. However, this is also much more code. In many embedded HTTP servers, post requests are not necessary at all, so we avoid this situation by simply limiting POST input data to no more than 10MB.

Another simplification of this simple server is the content-type of the return data. In the HTTP protocol, the server always sends the browser the MIME-Type of the data which it should be expecting. In writeSuccess(), you can see that this server always indicates a content-type of text/html. If you wish to return other content types, you will need to extend this method to allow your handler to supply a content type response before it sends data to the client.

Points of Interest

This SimpleHttpServer only implements a very bare-bones subset of even the basic HTTP/1.0 spec. Further revisions of the HTTP specification have included more complex and very valuable improvements, including compression, session keep alive, chunked responses, and lots more. However, because of the excellent and simple design of HTTP, you'll find that even this very bare-bones code is capable of serving pages which are compatible with modern web-browsers.

Other similar embeddable servers include:

History 

  • December 19, 2010: Initial version posted
  • December 22, 2010: Removed StreamReader from input side so we can properly get raw POST data 
  • March 2, 2012: corrected line-terminators to use WriteLine() for \r\n instead of just \n 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

David Jeske
United States United States
Member
David Jeske is an Entrepreneur and Computer Programmer, currently living in San Francisco, California.
 
He earned his B.S. Computer Engineering at University of Illnois at Champaign/Urbana (UIUC), and has worked at several Silicon Valley companies, including Google, Yahoo, eGroups.com, 3dfx, and Akklaim Entertainment. He has managed and architected extremely high-traffic websites, including Yahoo Groups and orkut.com, and has experience in a broad spectrum of technology areas including scalability, databases, drivers, system software, and 3d graphics.
 
You can contact him at davidj -a-t- gmail (dot) com for personal messages about this article.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberMatthysDT26 Mar '13 - 1:04 
Solid article, straight-forward and simple. Need more like this.
QuestionResponse to "barvalue" displays raw HTML on FireFox 19memberJames M OBrien23 Mar '13 - 7:58 
First, nice sample and it will server me well in a simple personal project.
 
I compiled and ran the sample using IE 9 and it ran as expected.
 
However, when I tried it with FireFox 19.0.2 and I clicked "barvalue", FireFox displayed the raw HTML response not the formatted response I saw on IE 9.
 
It has been far too long since I played with HTML for me to determine what is wrong.
 
I ask because my main browser will be either FireFox or Chrome on an Android tablet and unfortunately I'm not setup at the moment to test them.
 
Can you help?
 
Regards,
Jim O'Brien
AnswerRe: Response to "barvalue" displays raw HTML on FireFox 19 [modified]memberDavid Jeske23 Mar '13 - 16:53 
That's happening because of a small bug in my example handlePOSTRequest method. I forgot to call p.writeSuccess() which you have to always call, so it doesn't write a content-header response, and FIrefox is properly handling the fact that the response doesn't have a Content-Type: text/html header.
 
The code for that method should be...
 
public override void handlePOSTRequest(HttpProcessor p, StreamReader inputData) {
    Console.WriteLine("POST request: {0}", p.http_url);
    string data = inputData.ReadToEnd();
 
    p.writeSuccess();
    p.outputStream.WriteLine("<html><body><h1>test server</h1>");
    p.outputStream.WriteLine("<a href=/test>return</a><p>");
    p.outputStream.WriteLine("postbody: <pre>{0}</pre>", data);
}
 
I just submitted a fix to the download.

modified 23 Mar '13 - 23:12.

GeneralMy vote of 5membermr colorbooks14 Nov '12 - 13:11 
i am using it, thanks.
Questionfnc call to other classmemberfgdf24 Aug '12 - 23:31 
Hello, sorry - this may be a very simple question but I worked on it over houres. Cry | :((
 
How can I call a function (non static) in the 'TestMain' class (I my case a Form) from the 'handleGETRequest' fnc. The goal is, to give the receives parameters (GET or POST) to outside to trigger an action.
 
Thx, Pit
AnswerRe: fnc call to other classmemberDavid Jeske26 Aug '12 - 4:58 
This sounds like a C# question, not a question about httpserver. Read up on classes and instances. Keep in mind, in order to keep the code simple, the http server is written as a code sample you should expect to edit and customize - not a canned library.
 
When is the class instance allocated? (Once for the server, or once for each request?)
 
If it is once per server, you can change the code to hand it to the request processor when it's created.
 
If it is once per request, it can be allocated in the request handler.
GeneralThanks for your help, [modified]memberseanzcan31 Jul '12 - 5:50 
Many thanks for your help, I got the file uploading working now.

modified 31 Jul '12 - 12:12.

AnswerRe: Any help why the server cannot be accessed on Win 7/2K8 ?memberDavid Jeske31 Jul '12 - 5:58 
I know you said it's not a firewall issue, but it sounds like a firewall issue. Check your anti-virus, and make sure windows firewall has the application white-listed.
 
What port are you trying to run your http server on?
 
Another possible issue, is to make sure you have sufficient privilege for the port you are trying to serve on. Normally ports below 1000 (including the "standard" port 80) are "privileged" and require root/Administrator to listen on. This is why the demo defaults to port 8080. If you switched this to the standard port-80, try switching it to port 8080, and if that works, you may need to "run as administrator" to run on port 80.
 
Another possible issue if you are trying to use port-80, is that another program may be running and listening on that port. Though in this case I wouldn't expect for you to see "unable to connect..."
 
Double check that you have the right IP address by pinging the target server first.
 
Here are some links that may help.. Please post back when you find the solution to your problem...
 
http://www.sitepoint.com/unblock-port-80-on-windows-run-apache/[^]
 
http://answers.microsoft.com/en-us/windows/forum/windows_7-networking/port-80-windows-7-64bit/5cc1e5f3-408f-4370-acd2-8c266d13cc88[^]
 
http://maximumpcguides.com/windows-7/open-a-port-in-windows-7s-firewall/[^]
 
http://stackoverflow.com/questions/6872576/how-to-open-port-80-for-apache-previously-occupied-by-visual-studios-servers[^]
GeneralRe: Any help why the server cannot be accessed on Win 7/2K8 ?memberseanzcan31 Jul '12 - 6:15 
Thanks for your reply. I figured the problem came from the ipv6, but truly sorry that I was looking at server other http server posts as well and got missed up a bit. But thanks for your help.
GeneralGreat work! Can you give some hits how to let it accept file upload?memberseanzcan27 Jul '12 - 8:04 
Thanks for the great work. I am wandering how can I use it to accept a file upload from WebClient:
 
private void Upload(string url, string filename)
{
     System.Net.WebClient client = new System.Net.WebClient();
     client.Headers.Add("Content-Type", "binary/octet-stream");
     client.UploadFile(url, "POST", filename);
}

GeneralRe: Great work! Can you give some hits how to let it accept file upload?memberDavid Jeske27 Jul '12 - 9:34 
The first thing you'll need to do is supply a POST handler... Then the post data needs to be decoded... Here are some useful pointers.
 
http://www.vivtek.com/rfc1867.html[^]
 
http://www.codeguru.com/csharp/.net/net_asp/article.php/c19555/Understanding-File-Upload.htm[^]
 
http://www.ietf.org/rfc/rfc1867.txt[^]
 
Because there seems to be interest in a simple C# HTTP server that supports more features, I'll try to find the time to expand the example.
 
I think the most interesting tidbits are probably: basic host/URL mapping, directory-file-serving, html templating, and richer CGI support including file upload, connection-keep-alive, and server-push. If there are other elements of HTTP serving folks are interested in, reply and I'll see what I can do to include something about them. It's going to be a careful dance, as the current example is simple by design, so it's very easy to understand what it's doing.
GeneralRe: Great work! Can you give some hits how to let it accept file upload?memberseanzcan28 Jul '12 - 2:13 
Thanks for your help. A simple embedded http server with most common features could be used in many cases. I will dig into the provided information, hope figure it out soon. thanks again!
GeneralMy vote of 4memberRamya.Raju.M1 Jul '12 - 22:32 
good
QuestionHow to get Client IP?memberlgwait27 Jun '12 - 22:22 
How to get Client IP?
AnswerRe: How to get Client IP?memberDavid Jeske8 Jul '12 - 16:30 
This code-fragment should get you headed in the right direction..
 
public override void handleGETRequest(HttpProcessor p) {
    Console.WriteLine("request: {0}", p.http_url);
 
    EndPoint ep = p.socket.Client.RemoteEndPoint;
    if (ep.AddressFamily == AddressFamily.InterNetwork) {
        Console.WriteLine("Address: {0}",ep.ToString());
    }

QuestionSTOP and START the servermemberchienlim18 Apr '12 - 22:41 
How to implement a START and STOP functions of the server?
QuestionHttpWebRequest "POST" return error messagememberchienlim23 Mar '12 - 12:49 
When I use HttpWebRequest to POST a request to the SimpleHttpServer, I received a error message:
"The server committed a protocol violation. Section=ResponseStatusLine"
 
Please help, here is the code:
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(url);
 
            httpRequest.Method = "POST";
            httpRequest.ContentType = null;
 
            byte[] bytedata = Encoding.UTF8.GetBytes(poststring);
            httpRequest.ContentLength = bytedata.Length;
 
            Stream requestStream = httpRequest.GetRequestStream();
            requestStream.Write(bytedata, 0, bytedata.Length);
            requestStream.Close();
 

            HttpWebResponse httpWebResponse =
            (HttpWebResponse)httpRequest.GetResponse();
 
The url is just http://127.0.0.1:8080/ and the poststring is set to "foo=foovalue&bar=barvalue"
AnswerRe: HttpWebRequest "POST" return error messagememberDavid Jeske23 Mar '12 - 20:15 
If you read the other comments, there is a discussion of this issue..
 
[^]
 
You can either tell WebRequest to be less strict, or you can fix the bug. When I looked into this before, it just required you change the write calls from "\n" terminated to "\r\n" terminated. If there is another protocol bug, I'll need to look into it to provide a more detailed answer.
GeneralRe: HttpWebRequest "POST" return error messagememberABurkard26 Mar '12 - 11:08 
In the POST handler the writeSuccess() is missing.
 
Here is the correct code:
 
public override void handlePOSTRequest(HttpProcessor p, StreamReader inputData) {
            Console.WriteLine("POST request: {0}", p.http_url);
            string data = inputData.ReadToEnd();
            p.writeSuccess();         
            p.outputStream.WriteLine("<html><body><h1>test server</h1>");
            p.outputStream.WriteLine("<a href=/test>return</a><p>");
            p.outputStream.WriteLine("postbody: <pre>{0}
", data);
p.outputStream.WriteLine("</body></html>");
 
}
 
Andre
GeneralRe: HttpWebRequest "POST" return error messagememberchienlim29 Mar '12 - 15:03 
That works! Thanks.
GeneralRe: HttpWebRequest "POST" return error messagememberDavid Jeske23 Mar '13 - 17:13 
Looks like someone else ran into this problem too, so I just submitted a fix to the download.
QuestionImage Handlingmemberd4v1dza22 Jan '12 - 7:12 
Great simple system! One question though, how do I go about serving images through this system. I've got it working perfectly for html content, and because of Mozilla browser security, I can't just link to the images with file:///
AnswerRe: Image HandlingmemberDavid Jeske1 Mar '12 - 13:20 
You should just serve the images via HTTP as well... In order for the browser to properly handle the image, you'll need to serve a different mimetype in the HTTP response headers.
 
Look at writeSuccess(), at it sends the http response header. You can either change it, or make a new method.. either way you'll want code to write a header like:
 
HTTP/1.0 200 OK
Content-Type: image/jpeg
Connection: close
 
Then just spit out the bits of your jpeg image. You can find out the proper mime type for an arbitrary file extension using the windows content-type registry...
 
http://stackoverflow.com/questions/1910097/content-type-by-extension
GeneralRe: Image Handlingmemberd4v1dza1 Mar '12 - 20:56 
Thanks for the reply.
 
I am by trade a PHP programmer, quite new to c#. I know about changing the mime type for the different content, my problem lies in that I do not know how to actually open and serve up the image. I have tried a few methods after doing some research, but none have been successful.
GeneralRe: Image HandlingmemberDavid Jeske2 Mar '12 - 6:36 
I see. This isn't a question about HTTP then but just about how to open a stream and send it's data to another stream.
 
// If you are using .NET 4.0 you can use:
Stream imagedata = File.Open("foo.jpg",FileMode.Open);
imagedata.CopyTo(outputStream.BaseStream);
 
// otherwise use...
StreamReader filereader = new StreamReader(File.Open("foo.jpg",FileMode.Open));
outputStream.Write(reader.ReadToEnd());
 
That second version is non-ideal because it's loading the entire image into memory before sending. If you need a pre-4.0 solution that's better, the best I've seen is making an extension method CopyTo(). See the first link below.
 
http://stackoverflow.com/questions/230128/best-way-to-copy-between-two-stream-instances-c-sharp[^]
 
http://msdn.microsoft.com/en-us/library/b9skfh7s.aspx[^]

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 24 Mar 2013
Article Copyright 2010 by David Jeske
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid