Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#

Simple HTTP Server in C#

Rate me:
Please Sign up or sign in to vote.
4.94/5 (75 votes)
23 Mar 2013Apache5 min read 850.1K   31.7K   224   171
Threaded synchronous HTTP Server abstract class, to respond to HTTP requests

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...

C#
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.

C#
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.

C#
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.

C#
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:

C#
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 Apache License, Version 2.0


Written By
United States United States
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.

Comments and Discussions

 
QuestionRe: Including JavaScript & CSS Pin
umar.techBOY31-Aug-16 4:09
umar.techBOY31-Aug-16 4:09 
AnswerRe: Including JavaScript & CSS Pin
umar.techBOY31-Aug-16 19:09
umar.techBOY31-Aug-16 19:09 
Questionsend header Pin
filmee2431-Dec-14 2:34
filmee2431-Dec-14 2:34 
AnswerRe: send header Pin
David Jeske31-Dec-14 10:31
David Jeske31-Dec-14 10:31 
GeneralRe: send header Pin
filmee2431-Dec-14 11:07
filmee2431-Dec-14 11:07 
GeneralRe: send header Pin
David Jeske31-Dec-14 12:20
David Jeske31-Dec-14 12:20 
GeneralRe: send header Pin
filmee2431-Dec-14 23:51
filmee2431-Dec-14 23:51 
GeneralRe: send header Pin
David Jeske1-Jan-15 9:47
David Jeske1-Jan-15 9:47 
can i use more than 1 response code?


Each HTTP response can only have a single response code. Normally it is "200 OK" meaning the page is valid and should be accepted by the client. "404 NOT FOUND" says the URL was invalid, and "302 Redirect" says the requested resource has moved to a new URL and the browser should go there.

when i implement scripting, how can i redirect?


If you wish to redirect from Javascript inside the browser, you can use window.location.href = "url"; in javascript.

how can i use cookies and sessions?


You set a cookie by adding a cookie header, and that cookie will be returned to you as a header on subsequent requests. For example, if the server adds a header "Set-Cookie: foo=bar", then on the next request to the same server, the client will add the header "Cookie: foo=bar". You can read more about the details at the links below.

Sessions are not a feature of HTTP, but a method of managing state across multiple http-page-load-actions. For example, if you wish to preserve a shopping cart for a user, one method is to put the entire contents of the shopping cart into a cookie. Each time the user fetches a new page, you can decode the shopping cart-cookie to know what is in his cart. However, if the user never revisits the site, we have no record of what was in the cart. If he visits from a different machine, then his cart is empty. Another way to manage this type of state is with a login-cookie or session-cookie. You set only a single small cookie which is merely an *identifier*, either of the user, or a random ID which represents his "current session". Then, you store the contents of the shopping cart server-side, in your database. Each time the user visits a page, you see his session-id-cookie, you look that up in the database, and you can know the state of his cart (and any other session state).


http://en.wikipedia.org/wiki/HTTP_cookie[^]

http://tools.ietf.org/html/rfc2109[^]

http://tools.ietf.org/html/rfc6265[^]
GeneralRe: send header Pin
filmee241-Jan-15 10:00
filmee241-Jan-15 10:00 
GeneralRe: send header Pin
David Jeske1-Jan-15 11:05
David Jeske1-Jan-15 11:05 
Suggestionshutdown Pin
Robb Sadler26-Nov-14 5:03
Robb Sadler26-Nov-14 5:03 
QuestionHow to embed image in html? Pin
SWz18-Oct-14 3:12
SWz18-Oct-14 3:12 
AnswerRe: How to embed image in html? Pin
David Jeske8-Oct-14 12:30
David Jeske8-Oct-14 12:30 
GeneralRe: How to embed image in html? Pin
SWz18-Oct-14 18:27
SWz18-Oct-14 18:27 
QuestionAdding image streaming capabilities Pin
Member 110688897-Oct-14 8:43
Member 110688897-Oct-14 8:43 
AnswerRe: Adding image streaming capabilities Pin
David Jeske7-Oct-14 9:43
David Jeske7-Oct-14 9:43 
GeneralRe: Adding image streaming capabilities Pin
Member 110688897-Oct-14 10:53
Member 110688897-Oct-14 10:53 
GeneralRe: Adding image streaming capabilities Pin
David Jeske8-Oct-14 18:59
David Jeske8-Oct-14 18:59 
GeneralRe: Adding image streaming capabilities Pin
Member 110688899-Oct-14 4:02
Member 110688899-Oct-14 4:02 
GeneralRe: Adding image streaming capabilities Pin
David Jeske9-Oct-14 6:28
David Jeske9-Oct-14 6:28 
GeneralRe: Adding image streaming capabilities Pin
Member 110688899-Oct-14 8:48
Member 110688899-Oct-14 8:48 
GeneralRe: Adding image streaming capabilities Pin
David Jeske9-Oct-14 23:05
David Jeske9-Oct-14 23:05 
GeneralRe: Adding image streaming capabilities Pin
Member 1106888910-Oct-14 0:18
Member 1106888910-Oct-14 0:18 
GeneralRe: Adding image streaming capabilities Pin
David Jeske12-Oct-14 17:37
David Jeske12-Oct-14 17:37 
GeneralRe: Adding image streaming capabilities Pin
Member 1106888912-Oct-14 22:45
Member 1106888912-Oct-14 22:45 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.