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()
{
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;
}
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
{
int bfndx = 0;
do
{
numberOfBytesRead = ns.Read(myReadBuffer, 0, <BR> myReadBuffer.Length);
myCompleteMessage =
String.Concat(myCompleteMessage,
Encoding.ASCII.GetString(myReadBuffer, 0, <BR> numberOfBytesRead));
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:
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;
}
}
while(ndx < numberOfBytesRead);
}
while(ns.DataAvailable);
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"));
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);
ns.Write(bHeadersString, 0, bHeadersString.Length);
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:
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!