Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / operating-systems / Windows

Sample HTTP Server Skeleton in C#

4.44/5 (32 votes)
14 Jan 20072 min read 1   12.2K  
HTTP Server Abstract class, to serve HTTP Content.

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:

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

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