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

Sample HTTP Server Skeleton in C#

By , 13 Jan 2007
 

Sample HTTPServer.

Introduction

We need to serve HTTP content, to a regular browser, or another HTTP client from our application. Lets create an abstract class for handling the HTTP request, and let the derived class handle the response, without worrying about anything else.

Using the code

We will use two classes to build our HTTP Server Skeleton: CSHTTPServer class and CsHTTPRequest class. CSHTTPServer will be the parent for each of the CsHTTPRequest requests, and will contain the server information as the listening port, the listener socket, the instance running Thread, the response statuses, and server name. It will implement the methods for starting, stopping and resuming the server, and it will define the abstract OnResponse method, that should be implemented in derived classes for actual content serving.

Lets create the CSHTTPServer class:

  
public abstract class CSHTTPServer
{
  private int portNum = 8080;
  private TcpListener listener;
  System.Threading.Thread Thread;

  public Hashtable respStatus;

  public string Name = "MyHTTPServer/1.0.*";

  public bool IsAlive
  {
     get 
     {
       return this.Thread.IsAlive; 
     }
  }

  public CSHTTPServer()
  {
     //
     respStatusInit();
  }

  public CSHTTPServer(int thePort)
  {
     portNum = thePort;
     respStatusInit();
  }

  private void respStatusInit()
  {
     respStatus = new Hashtable();
     
     respStatus.Add(200, "200 Ok");
     respStatus.Add(201, "201 Created");
     respStatus.Add(202, "202 Accepted");
     respStatus.Add(204, "204 No Content");

     respStatus.Add(301, "301 Moved Permanently");
     respStatus.Add(302, "302 Redirection");
     respStatus.Add(304, "304 Not Modified");
     
     respStatus.Add(400, "400 Bad Request");
     respStatus.Add(401, "401 Unauthorized");
     respStatus.Add(403, "403 Forbidden");
     respStatus.Add(404, "404 Not Found");

     respStatus.Add(500, "500 Internal Server Error");
     respStatus.Add(501, "501 Not Implemented");
     respStatus.Add(502, "502 Bad Gateway");
     respStatus.Add(503, "503 Service Unavailable");
  }

  public void Listen() 
  {
     bool done = false;
    
     listener = new TcpListener(portNum);
     
     listener.Start();

     WriteLog("Listening On: " + portNum.ToString());

     while (!done) 
     {
       WriteLog("Waiting for connection...");
       CsHTTPRequest newRequest<BR>                      = new CsHTTPRequest(listener.AcceptTcpClient(),this);
       Thread Thread = new Thread(new ThreadStart(newRequest.Process));
       Thread.Name = "HTTP Request";
       Thread.Start();
     }

  }
   
  public void WriteLog(string EventMessage)
  {
     Console.WriteLine(EventMessage);
  }

  public void Start()
  {
     // CSHTTPServer HTTPServer = new CSHTTPServer(portNum);
     this.Thread = new Thread(new ThreadStart(this.Listen));
     this.Thread.Start();
  }

  public void Stop()
  {
     listener.Stop();
     this.Thread.Abort();
  }

  public void Suspend()
  {
     this.Thread.Suspend();
  }

  public void Resume()
  {
     this.Thread.Resume();
  }

  public abstract void OnResponse(ref HTTPRequestStruct rq, <BR>                                  ref HTTPResponseStruct rp);

}
  

Once the server have being started by calling the method Start(), control is passed to the Listen() method running in a new thread, which in turns creates a new thread to run newRequest.Process(), for every new listener accepted request.

Now create the CsHTTPRequest class:

       
   enum RState
   {
      METHOD, URL, URLPARM, URLVALUE, VERSION, 
      HEADERKEY, HEADERVALUE, BODY, OK
   };

   enum RespState
   {
      OK = 200, 
      BAD_REQUEST = 400,
      NOT_FOUND = 404
   }

   public struct HTTPRequestStruct
   {
      public string Method;
      public string URL;
      public string Version;
      public Hashtable Args;
      public bool Execute;
      public Hashtable Headers;
      public int BodySize;
      public byte[] BodyData;
   }

   public struct HTTPResponseStruct
   {
      public int status;
      public string version;
      public Hashtable Headers;
      public int BodySize;
      public byte[] BodyData;
      public System.IO.FileStream fs;
   }

   /// <SUMMARY>
   /// Summary description for CsHTTPRequest.
   /// </SUMMARY>
   public class CsHTTPRequest
   {
      private TcpClient client;

      private RState ParserState;

      private HTTPRequestStruct HTTPRequest;

      private HTTPResponseStruct HTTPResponse;

      byte[] myReadBuffer;

      CSHTTPServer Parent;

      public CsHTTPRequest(TcpClient client, CSHTTPServer Parent) 
      {
         this.client = client;
         this.Parent = Parent;

         this.HTTPResponse.BodySize = 0;
      }

      public void Process()
      {
         myReadBuffer = new byte[client.ReceiveBufferSize];
         String myCompleteMessage = "";
         int numberOfBytesRead = 0;

         Parent.WriteLog("Connection accepted. Buffer: " + <BR>                         client.ReceiveBufferSize.ToString());
         NetworkStream ns = client.GetStream();

         string hValue = "";
         string hKey = "";

         try 
         {
            // binary data buffer index
            int bfndx = 0;

            // Incoming message may be larger than the buffer size.
            do
            {
               numberOfBytesRead = ns.Read(myReadBuffer, 0, <BR>                                           myReadBuffer.Length);  
               myCompleteMessage = 
                  String.Concat(myCompleteMessage, 
                     Encoding.ASCII.GetString(myReadBuffer, 0, <BR>                                              numberOfBytesRead));  
               
               // read buffer index
               int ndx = 0;
               do
               {
                  switch ( ParserState )
                  {
                     case RState.METHOD:
                        if (myReadBuffer[ndx] != ' ')
                           HTTPRequest.Method += (char)myReadBuffer[ndx++];
                        else 
                        {
                           ndx++;
                           ParserState = RState.URL;
                        }
                        break;
                     case RState.URL:
                        if (myReadBuffer[ndx] == '?')
                        {
                           ndx++;
                           hKey = "";
                           HTTPRequest.Execute = true;
                           HTTPRequest.Args = new Hashtable();
                           ParserState = RState.URLPARM;
                        }
                        else if (myReadBuffer[ndx] != ' ')
                           HTTPRequest.URL += (char)myReadBuffer[ndx++];
                        else
                        {
                           ndx++;
                           HTTPRequest.URL<BR>                                    = HttpUtility.UrlDecode(HTTPRequest.URL);
                           ParserState = RState.VERSION;
                        }
                        break;
                     case RState.URLPARM:
                        if (myReadBuffer[ndx] == '=')
                        {
                           ndx++;
                           hValue="";
                           ParserState = RState.URLVALUE;
                        }
                        else if (myReadBuffer[ndx] == ' ')
                        {
                           ndx++;

                           HTTPRequest.URL<BR>                                    = HttpUtility.UrlDecode(HTTPRequest.URL);
                           ParserState = RState.VERSION;
                        }
                        else
                        {
                           hKey += (char)myReadBuffer[ndx++];
                        }
                        break;
                     case RState.URLVALUE:
                        if (myReadBuffer[ndx] == '&')
                        {
                           ndx++;
                           hKey=HttpUtility.UrlDecode(hKey);
                           hValue=HttpUtility.UrlDecode(hValue);
                           HTTPRequest.Args[hKey] =  <BR>                                HTTPRequest.Args[hKey] != null ? 
                                    HTTPRequest.Args[hKey] + ", " + hValue : <BR>                                    hValue;
                           hKey="";
                           ParserState = RState.URLPARM;
                        }
                        else if (myReadBuffer[ndx] == ' ')
                        {
                           ndx++;
                           hKey=HttpUtility.UrlDecode(hKey);
                           hValue=HttpUtility.UrlDecode(hValue);
                           HTTPRequest.Args[hKey] = <BR>                                HTTPRequest.Args[hKey] != null ?
                                   HTTPRequest.Args[hKey] + ", " + hValue : <BR>                                   hValue;
                           
                           HTTPRequest.URL<BR>                                   = HttpUtility.UrlDecode(HTTPRequest.URL);
                           ParserState = RState.VERSION;
                        }
                        else
                        {
                           hValue += (char)myReadBuffer[ndx++];
                        }
                        break;
                     case RState.VERSION:
                        if (myReadBuffer[ndx] == '\r') 
                           ndx++;
                        else if (myReadBuffer[ndx] != '\n') 
                           HTTPRequest.Version += (char)myReadBuffer[ndx++];
                        else 
                        {
                           ndx++;
                           hKey = "";
                           HTTPRequest.Headers = new Hashtable();
                           ParserState = RState.HEADERKEY;
                        }
                        break;
                     case RState.HEADERKEY:
                        if (myReadBuffer[ndx] == '\r') 
                           ndx++;
                        else if (myReadBuffer[ndx] == '\n')
                        {
                           ndx++;
                           if (HTTPRequest.Headers["Content-Length"] != null)
                           {
                              HTTPRequest.BodySize = 
                       Convert.ToInt32(HTTPRequest.Headers["Content-Length"]);
                              this.HTTPRequest.BodyData<BR>                                       = new byte[this.HTTPRequest.BodySize];
                              ParserState = RState.BODY;
                           }
                           else
                              ParserState = RState.OK;
                           
                        }
                        else if (myReadBuffer[ndx] == ':')
                           ndx++;
                        else if (myReadBuffer[ndx] != ' ')
                           hKey += (char)myReadBuffer[ndx++];
                        else 
                        {
                           ndx++;
                           hValue = "";
                           ParserState = RState.HEADERVALUE;
                        }
                        break;
                     case RState.HEADERVALUE:
                        if (myReadBuffer[ndx] == '\r') 
                           ndx++;
                        else if (myReadBuffer[ndx] != '\n')
                           hValue += (char)myReadBuffer[ndx++];
                        else 
                        {
                           ndx++;
                           HTTPRequest.Headers.Add(hKey, hValue);
                           hKey = "";
                           ParserState = RState.HEADERKEY;
                        }
                        break;
                     case RState.BODY:
                        // Append to request BodyData
                        Array.Copy(myReadBuffer, ndx, <BR>                           this.HTTPRequest.BodyData, 
                           bfndx, numberOfBytesRead - ndx);
                        bfndx += numberOfBytesRead - ndx;
                        ndx = numberOfBytesRead;
                        if ( this.HTTPRequest.BodySize <=  bfndx)
                        {
                           ParserState = RState.OK;
                        }
                        break;
                        //default:
                        //   ndx++;
                        //   break;

                  }
               }
               while(ndx < numberOfBytesRead);

            }
            while(ns.DataAvailable);

            // Print out the received message to the console.
            Parent.WriteLog("You received the following message : \n" +
               myCompleteMessage);
            
            HTTPResponse.version = "HTTP/1.1";

            if (ParserState != RState.OK)
               HTTPResponse.status = (int)RespState.BAD_REQUEST;
            else
               HTTPResponse.status = (int)RespState.OK;

            this.HTTPResponse.Headers = new Hashtable();
            this.HTTPResponse.Headers.Add("Server", Parent.Name);
            this.HTTPResponse.Headers.Add("Date", DateTime.Now.ToString("r"));
            
            // if (HTTPResponse.status == (int)RespState.OK)
            this.Parent.OnResponse(ref this.HTTPRequest, <BR>                                   ref this.HTTPResponse);

            string HeadersString = this.HTTPResponse.version + " " 
               + this.Parent.respStatus[this.HTTPResponse.status] + "\n";
            
            foreach (DictionaryEntry Header in this.HTTPResponse.Headers) 
            {
               HeadersString += Header.Key + ": " + Header.Value + "\n";
            }

            HeadersString += "\n";
            byte[] bHeadersString = Encoding.ASCII.GetBytes(HeadersString);

            // Send headers   
            ns.Write(bHeadersString, 0, bHeadersString.Length);
   
            // Send body
            if (this.HTTPResponse.BodyData != null)
            ns.Write(this.HTTPResponse.BodyData, 0, <BR>                     this.HTTPResponse.BodyData.Length);

            if (this.HTTPResponse.fs != null)
               using (this.HTTPResponse.fs) 
               {
                  byte[] b = new byte[client.SendBufferSize];
                  int bytesRead;
                  while ((bytesRead<BR>                              = this.HTTPResponse.fs.Read(b,0,b.Length)) > 0) 
                  {
                     ns.Write(b, 0, bytesRead);
                  }
                  
                  this.HTTPResponse.fs.Close();
               }
   
         }
         catch (Exception e) 
         {
            Parent.WriteLog(e.ToString());
         }
         finally 
         {
            ns.Close();
            client.Close();
            if (this.HTTPResponse.fs != null)
               this.HTTPResponse.fs.Close();
            Thread.CurrentThread.Abort();
         }
      }
            
   }

The Process() method, parses the HTTP request, and if no bad request is found then the parent HTTP server OnResponse method is called passing the request and response variables, to be processed. Finally if a response is found, it is served to the client.

Here is a diagram for the HTTP protocol that I hope will help you understand the switch statement:

Sample Image - maximum width is 600 pixels

Points of Interest

That’s it! It works for me. I use this abstract class to create a tiny HTTP Server. We use some tricks to create a different look for the application than that provided by default, and you will find the way to create a tray icon too.

History

The total size of the sample and source were drastically lessen. Fixed bug: The files response were truncated due to bad size buffer!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

rmortega77
Software Developer CIMEX S.A.
Cuba Cuba
Member
Rodolfo Ortega is a Cuban Computer Scientist. He works as IT Auditor for the CIMEX S.A. subsidiary in Holguin, Cuba. He lives and works in Holguin, in the eastern part of the island of Cuba.

You can contact him at rodolfom[]cimex.com.cu for any personal message: Ideas on new articles, bibliography about new APIs, questions, are wellcome.

Submit questions related with current article to the article forum.

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   
QuestionI want to make this type of server in windows mobile emulator. how can i start ???memberraythatha728 Feb '13 - 23:22 
Hello, sir this is the nice post. I really appreciate you. but i want to make this HTTP server on windows mobile so can you help me please..
i am beginner in windows mobile..
 

 
Thank you.
GeneralMy vote of 5memberarthur zamarin13 Jan '13 - 3:13 
Thank you.
Best http server code for C#.
GeneralMy vote of 3memberdemjan25 Nov '12 - 0:49 
Method "Process" is too long, I think it should be split into several methods
Suggestionserver:8080/..%2f exploit [modified]member1337TREE22 Aug '12 - 10:43 
if you use ..%2f you can go up directories and access potentially harmfull files.
..%2f is ../ encoded.
 
to fix it add the following code after string path = this.Folder + "\\" + rq.URL.Replace("/","\\"); in myserver.cs
 

                  if (path.Contains(".."))
                  {
                             string bodyStr = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n";
                         bodyStr += "<HTML><HEAD>\n";
                         bodyStr += "<META http-equiv=Content-Type content=\"text/html; charset=windows-1252\">\n";
                         bodyStr += "</HEAD>\n";
                         bodyStr += "<BODY><p>The requested URL conatined \"..\" used for many malicious attacks.<p>\n";
                         bodyStr += "</BODY></HTML>\n";
 
                         rp.BodyData = Encoding.ASCII.GetBytes(bodyStr);
                         return;
                  }
--1337TREE

-- modified 22 Aug '12 - 16:58.
Generalvery good examplemembergpresti@hotmail.com26 Jul '11 - 5:23 
detailed enough and not big
 
thanks to rmortega77
General5 starsmember=Quaqmire=17 Mar '11 - 9:17 
Excellent, thank you man!
 
This opens endless possibilities to me.
Srew Apache and screw IIS now, lol Smile | :)
Generalimportant bug to fix.... [modified]memberDavid Jeske19 Dec '10 - 17:31 
I appreciate that the author took the time to write this article sample.
 
Unfortunatly I think it has an important race bug. Using "while(ns.DataAvailable)" to decide when to end processing creates a possible race condition in any type of request (though you would probably only see it in POST requests most of the time). For example, consider a request where when the server-processes the first loop there is only one byte in the TCP stream, this byte will be consumed, and ns.DataAvailable will return false, and the entire request parsing will break.
 
To fix this, it needs to be something like "while (ParserState != RState.OK)"
 
Inspired by this article, I wrote a similar Simple HTTP Server in C# that uses function/stack state instead of a state machine. This makes it a bit easier to follow and easier to modify.
modified on Monday, December 20, 2010 3:07 AM

GeneralMy vote of 1memberDavid Jeske19 Dec '10 - 17:29 
the code is very buggy and does not implement HTTP properly
GeneralHardcoded Variablesmemberbmnot3 Feb '10 - 6:06 
Hi..
 
First , thanks for a great piece of code. It sure makes it a lot easier for all of us here. I just want to point out to the fact that there seems to be a problem with the SaveSettings method in the configuration class. The variable that you use to update the XML files are all hardcoded in the class. So this way the method SaveSettings never actually saves the user options on the Main windows form.
 
I also need a help with how to determin the IP of the requesting client so that I can implement a custom logger.
 
Thanks
QuestionIs it possible to add HTTPS support to this HTTP server skeletonmemberTal Kapon New20 Jan '10 - 3:30 
Hi
 
We need to have a super simple HTTP server process that can accept HTTP requests. Our problem is that this server should be secured. How can I add this support to the skeleton?
 
Thx
GeneralThanks for your the articlememberdfg147815 Nov '09 - 21:52 
Hi,Tnanks for your help.I have get a lot for the example.But i think we can give more detail information for reader.For example,we can tell how to deploy and use it,if the reader is a beginner.
Add follow as the details:
1.run the http server
2.select the valid folder,the same as the IIS set virtual folder
3.type port information,for example 8080.The same as we need set a port
by iis publish a web site.
4.visite the url by brower:http://127.0.0.1:8080
you will see the surprise.lol
 
thanks
GeneralRe: Thanks for your the articlememberdfg147818 Nov '09 - 14:26 
I found a debug.When the application firstly run and locate in the start station.And then we click stop button. It will raise a bug:WSACancelBlockingCall's call abort.
So that i modified the follow as code in CsHttpServer.cs:
public void Listen()
{ bool done = false;
listener = new TcpListener(portNum);
listener.Start();
WriteLog("Listening On: " + portNum.ToString());
while (!done)
{
WriteLog("Waiting for connection...");
if (listener.Pending()) { CsHTTPRequest newRequest = new CsHTTPRequest(listener.AcceptTcpClient(),this);
Thread Thread = new Thread(new ThreadStart(newRequest.Process));
Thread.Name = "HTTP Request";
Thread.Start();
} }
 
}
GeneralVery useful -- Thanks!membermilkplus19 Oct '09 - 7:01 
I found your code very easy to read and useful for my own project WebConfig[^].
 
I also wanted to mention that there is another similar project to yours called Embedded .Net HTTP Server[^].
 
Best,
David
GeneralRun as client...membercharliesangels20 Nov '08 - 12:35 
Hello All:
 
I want to know how can I test this application?I was able to make the code run as a server (by simple clicking on the Start button) which is listening on port 8080 waiting for clients to connect. My question is how can I connect clients to this server? Is it also available in the code attached to this article? One other thing, what should I expect, as behavior, of this HTTP Server?Confused | :confused:
 
Help is much appreciate.
Thanks in advance,
Rgds.
GeneralroutingmemberAstyan8 Jan '08 - 8:45 
How can we routing the request that the server get (what is the variable to use etc )
to another http server (inside a local network for example )
(using another port to test the solution with two instances of this nice http server Smile | :) )
 
Thanks !!!
GeneralIP Addreesmemberwas_wahep31 Dec '07 - 6:20 
How i can know the ip address or MAC for the computer try connect me???

GeneralFix for DotNet WebRequest not working with this servermemberplan17b11 Aug '07 - 11:16 

If you try to use the DotNet WebRequest with this HTTP Server,
you will get a
 
"The server committed a protocol violation"
 
This is because the HTTP Repsonse Headers are separated witb a LineFeed
(\n) rather than a Carriage Return/Linefeed (\r\n).
 
You can edit this in CsHTTPRequest.cs, function: Process().
replace the "\n" termination strings with "\r\n".
 

This project has been a great help to me. Thanks very much.
 
Cheers.
 

GeneralIf you don't want to write your own...membericantfindanunsedname28 Jun '07 - 9:49 
There's a commercial solution available from www.neokernel.com, its a web application server for .NET that you can run from inside another .net app.
 
marcus
GeneralLine breaksmemberesag20 Mar '07 - 22:07 
Nice example, but I noticed an error in the code for writing the headers: they should be separated with "\r\n" not "\n".
 
I've made this mistake myself because in C/C++ "\n" produces carriage return and new line, whereas in C# it only gives a new line character.

 
/Erik
GeneralWonderful example, but...membergaudenz9 Feb '07 - 7:08 
it doesn't handle POST requests properly. Problem seems to be timing issues when reading from the NetworkStream. It seems to work when used via the local loopback interface (127.0.0.1), but not via a network interface card.
 
Problem is that the body of the POST request doesn't seem to be available in the NetworkStream at the time the read method is called in the csHTTPRequest.Process method. When adding a little delay, eg. Thread.Sleep(300) then it seems to work for certain cases, but not all.
 
Any ideas on how to solve this? I've tested it on Windows XP single core and dual core processor, both showing the same problem.
 
Thanks for all hints and ideas,
 
Gaudenz

GeneralRe: Wonderful example, but... [modified]memberDavid Jeske19 Dec '10 - 17:28 
I appreciate that the author took the time to write this article sample, but unfortunatly I think it has an important race bug...
 
Using "while(ns.DataAvailable)" to decide when to end processing creates a possible race condition in any type of request (though you would probably only see it in POST requests most of the time). For example, consider a request where when the server-processes the first loop there is only one byte in the TCP stream, this byte will be consumed, and ns.DataAvailable will return false, and the entire request parsing will break.
 
To fix this, it needs to be something like "while (ParserState != RState.OK)"
modified on Monday, December 20, 2010 3:05 AM

Generalvive la revolution!memberVertyg08 Jan '07 - 13:34 
Nice example, thanks!

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 14 Jan 2007
Article Copyright 2007 by rmortega77
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid