Introduction
Hello friends, in this article we will discuss about how to implement web server in c#. This project is done with .Net 4.0 and Visual Studio 2012.I am also uploading complete source code for better understanding and easy implementation. All required settings (like port no.) are mentioned in code. I have categorized this complete project into six sections, those we will discuss in implementation part. And most importantly this is my first online article, so please forgive me in case of any mistakes especially grammatical mistakes.
Background
The code is not complicated, but for better understanding of this article one should has basics idea of sockets and multithreading programing. In case of query you can simply google it or drop mail to me, I’ll definitely try to solve your query.
Implementation
As said earlier I have categorized this project in six sections, we will discuss all sections one by one in detail.
1)First section is server.
public class WebServer
{
private bool _running = false;
private int _timeout = 5;
private Encoding _charEncoder = Encoding.UTF8;
private Socket _serverSocket;
private string _contentPath;
private void InitializeSocket(IPAddress ipAddress, int port, string contentPath) {
_serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_serverSocket.Bind(new IPEndPoint(ipAddress, port));
_serverSocket.Listen(10); _serverSocket.ReceiveTimeout = _timeout;
_serverSocket.SendTimeout = _timeout;
_running = true; _contentPath = contentPath;
}
public void Start(IPAddress ipAddress, int port, string contentPath)
{
try
{
InitializeSocket(ipAddress, port, contentPath);
}
catch
{
Console.WriteLine("Error in creating server socker");
Console.ReadLine();
}
while (_running)
{
var requestHandler = new RequestHandler(_serverSocket, contentPath);
requestHandler.AcceptRequest();
}
}
public void Stop()
{
_running = false;
try
{
_serverSocket.Close();
}
catch
{
Console.WriteLine("Error in closing server or server already closed");
Console.ReadLine();
}
_serverSocket = null;
}
}
In this section I have created a server socket that will listen for web request continuously (while(running)).This section has three methods.For usage you have to use Start(IPAddress ipAddress, int port, string contentPath) and Stop() functions
NOTE : contentPath is path or directory that you want to host on server
- InitializeSocket:To create server socket that will listen to request continuously [while(_running)] with required properties like port no., receive and send timeout etc.
- Start : To start socket in listening mode
- Stop:To stop Socket listening
2)Second section is RequestHandler.
class RequestHandler
{
private Socket _serverSocket;
private int _timeout;
private string _contentPath;
private Encoding _charEncoder = Encoding.UTF8;
public RequestHandler(Socket serverSocket, String contentPath)
{
_serverSocket = serverSocket;
_timeout = 5;
_contentPath = contentPath;
}
public void AcceptRequest()
{
Socket clientSocket = null;
try
{
clientSocket = _serverSocket.Accept();
var requestHandler = new Thread(() =>
{
clientSocket.ReceiveTimeout = _timeout;
clientSocket.SendTimeout = _timeout;
HandleTheRequest(clientSocket);
});
requestHandler.Start();
}
catch
{
Console.WriteLine("Error in accepting client request");
Console.ReadLine();
if (clientSocket != null)
clientSocket.Close();
}
}
private void HandleTheRequest(Socket clientSocket)
{
var requestParser = new RequestParser();
string requestString = DecodeRequest(clientSocket);
requestParser.Parser(requestString);
if (requestParser.HttpMethod.Equals("get", StringComparison.InvariantCultureIgnoreCase))
{
var createResponse = new CreateResponse(clientSocket, _contentPath);
createResponse.RequestUrl(requestParser.HttpUrl);
}
else
{
Console.WriteLine("unemplimented mothode");
Console.ReadLine();
}
StopClientSocket(clientSocket);
}
public void StopClientSocket(Socket clientSocket)
{
if (clientSocket != null)
clientSocket.Close();
}
private string DecodeRequest(Socket clientSocket)
{
var receivedBufferlen = 0;
var buffer = new byte[10240];
try
{
receivedBufferlen = clientSocket.Receive(buffer);
}
catch (Exception)
{
Console.ReadLine();
}
return _charEncoder.GetString(buffer, 0, receivedBufferlen);
}
}
This section accepts request and creates new thread to handle the request. As we are handling only GET method there is check for type of request. This section has three important methods.
- AcceptRequest:This function accepts web request and creates a new thread to process a request.
- HandleTheRequest : This method checks for type of web request and then passes it to create response.
- DecodeRequest:This method is get request data, decode it and pass it to request parser.
3)Third section is RequestParser.
public class RequestParser
{
private Encoding _charEncoder = Encoding.UTF8;
public string HttpMethod;
public string HttpUrl;
public string HttpProtocolVersion;
public void Parser(string requestString)
{
try
{
string[] tokens = requestString.Split(' ');
tokens[1] = tokens[1].Replace("/", "\\");
HttpMethod = tokens[0].ToUpper();
HttpUrl = tokens[1];
HttpProtocolVersion = tokens[2];
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
Console.WriteLine(ex.InnerException.Message);
Console.WriteLine("Bad Request");
}
}
}
This section gets decoded request data from request handler. It has only method which returns request as array of string after parsing. I think creating an string array for every request is not a good approach because currently I am using only first three strings from array, so lot space is used for no reason.
4)Fourth section is CreateResponse.
public class CreateResponse
{
RegistryKey registryKey = Registry.ClassesRoot;
public Socket ClientSocket = null;
private Encoding _charEncoder = Encoding.UTF8;
private string _contentPath ;
public FileHandler FileHandler;
public CreateResponse(Socket clientSocket,string contentPath)
{
_contentPath = contentPath;
ClientSocket = clientSocket;
FileHandler=new FileHandler(_contentPath);
}
public void RequestUrl(string requestedFile)
{
int dotIndex = requestedFile.LastIndexOf('.') + 1;
if (dotIndex > 0)
{
if (FileHandler.DoesFileExists(requestedFile)) SendResponse(ClientSocket, FileHandler.ReadFile(requestedFile), "200 Ok", GetTypeOfFile(registryKey, (_contentPath + requestedFile)));
else
SendErrorResponce(ClientSocket); }
else {
if (FileHandler.DoesFileExists("\\index.htm"))
SendResponse(ClientSocket, FileHandler.ReadFile("\\index.htm"), "200 Ok", "text/html");
else if (FileHandler.DoesFileExists("\\index.html"))
SendResponse(ClientSocket, FileHandler.ReadFile("\\index.html"), "200 Ok", "text/html");
else
SendErrorResponce(ClientSocket);
}
}
private string GetTypeOfFile(RegistryKey registryKey,string fileName)
{
RegistryKey fileClass = registryKey.OpenSubKey(Path.GetExtension(fileName));
return fileClass.GetValue("Content Type").ToString();
}
private void SendErrorResponce(Socket clientSocket)
{
SendResponse(clientSocket, null, "404 Not Found", "text/html");
}
private void SendResponse(Socket clientSocket, byte[] byteContent, string responseCode, string contentType)
{
try
{
byte[] byteHeader = CreateHeader(responseCode, byteContent.Length, contentType);
clientSocket.Send(byteHeader);
clientSocket.Send(byteContent);
clientSocket.Close();
}
catch
{
}
}
private byte[] CreateHeader(string responseCode, int contentLength, string contentType)
{
return _charEncoder.GetBytes("HTTP/1.1 " + responseCode + "\r\n"
+ "Server: Simple Web Server\r\n"
+ "Content-Length: " + contentLength + "\r\n"
+ "Connection: close\r\n"
+ "Content-Type: " + contentType + "\r\n\r\n");
}
}
This section checks for requested file, creates a response and send it client socket.
- RequestUrl:This method sends response to client socket.
- GetTypeOfFile : This method gets type of requested file from Registry.ClassesRoot [eg.: for .html ,file type is text/html etc]
- SendResponse:This method sends response to client socket.
- CreateHeader:This method creates response header.
5)Fifth section is FileHandler .
public class FileHandler
{
private string _contentPath;
public FileHandler(string contentPath)
{
_contentPath = contentPath;
}
internal bool DoesFileExists(string directory)
{
return File.Exists(_contentPath+directory);
}
internal byte[] ReadFile(string path)
{
if (ServerCache.Contains(_contentPath+path))
{
Console.WriteLine("cache hit");
return ServerCache.Get(_contentPath+path);
}
else
{
byte[] content = File.ReadAllBytes(_contentPath+path);
ServerCache.Insert(_contentPath+path, content);
return content;
}
}
}
This section checks for requested file and if file is found then returns file as a byte array.
- DoesFileExists:This method checks whether requested file is present or not.
- ReadFile : This method reads content of file and returns as a byte array.
6)Sixth section is ServerCache .
class ServerCache
{
public struct Content
{
internal byte[] ResponseContent;
internal int RequestCount;
};
private static readonly object SyncRoot = new object();
private static int _capacity = 15;
private static Dictionary<string, content=""> _cache = new Dictionary<string,>(StringComparer.OrdinalIgnoreCase) { };
public static bool Insert(string url, byte[] body)
{
lock (SyncRoot)
{
if (IsFull())
CreateEmptySpace();
var content = new Content {RequestCount = 0, ResponseContent = new byte[body.Length]};
Buffer.BlockCopy(body, 0, content.ResponseContent, 0, body.Length);
if (!_cache.ContainsKey(url))
{
_cache.Add(url, content);
return false;
}
return true;
}
}
public static bool IsFull()
{
return _cache.Count >= _capacity;
}
public static byte[] Get(string url)
{
if (_cache.ContainsKey(url))
{
Content content = _cache[url];
content.RequestCount++;
_cache[url] = content;
return content.ResponseContent;
}
return null;
}
public static bool Contains(string url)
{
return _cache.ContainsKey(url);
}
private static void CreateEmptySpace()
{
var minRequestCount = Int32.MaxValue;
var url = String.Empty;
foreach (var entry in _cache)
{
Content content = entry.Value;
if (content.RequestCount < minRequestCount)
{
minRequestCount = content.RequestCount;
url = entry.Key;
}
}
_cache.Remove(url);
}
public static int CacheCount()
{
return _cache.Count;
}
}
</string,>
This section is optional. This is additional functionality provided to reduce file reading time. Here I have used Dictionary for caching most requested files. Dictionary key will be url of requested file and value is object with two properties, one is byte array for storing file content and second is integer to maintain count of respective file. I have not used any standard caching algorithm. We can also use cache object instead of this.
Usage
- For usage you have to use Start(IPAddress ipAddress, int port, string contentPath) and Stop()
- Provide port no. and Content path
- Send request from browser