Click here to Skip to main content
15,868,004 members
Articles / Web Development / HTML

Simple Web Server in c#

Rate me:
Please Sign up or sign in to vote.
4.91/5 (12 votes)
6 May 2014CPOL3 min read 61K   4.7K   51   10

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.

C#
public class WebServer
    {
        // check for already running
        private bool _running = false;
        private int _timeout = 5;
        private Encoding _charEncoder = Encoding.UTF8;
        private Socket _serverSocket;

        // Directory to host our contents
        private string _contentPath;
        
        //create socket and initialization
        private void InitializeSocket(IPAddress ipAddress, int port, string contentPath) //create socket
        {
            _serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _serverSocket.Bind(new IPEndPoint(ipAddress, port));
            _serverSocket.Listen(10);    //no of request in queue
            _serverSocket.ReceiveTimeout = _timeout;
            _serverSocket.SendTimeout = _timeout;
            _running = true; //socket created
            _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.

C#
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
            {
                // Create new thread to handle the request and continue to listen the socket.
                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.WriteLine("buffer full");
                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.

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

C#
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))    //If yes check existence of the file
                    SendResponse(ClientSocket, FileHandler.ReadFile(requestedFile), "200 Ok", GetTypeOfFile(registryKey, (_contentPath + requestedFile)));
                    else
                        SendErrorResponce(ClientSocket);      // We don't support this extension.
            }
            else   //find default file as index .htm of index.html
            {
                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 .

C#
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)
        {
            //return File.ReadAllBytes(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 .

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionssl Pin
fulvio9-Sep-20 23:18
fulvio9-Sep-20 23:18 
QuestionHow to use? Pin
hieutruong45523-Jun-20 18:07
hieutruong45523-Jun-20 18:07 
QuestionNaming Convention is not correct Pin
Member 1104700514-Jul-15 22:00
Member 1104700514-Jul-15 22:00 
SuggestionWhy did you write this aritcal Pin
Member 104100765-Jun-14 7:32
Member 104100765-Jun-14 7:32 
GeneralRe: Why did you write this aritcal Pin
Vladimir.R.L28-Jul-15 3:07
Vladimir.R.L28-Jul-15 3:07 
BugBug In CreateResponse.cs Pin
KevinAG12-May-14 7:57
KevinAG12-May-14 7:57 
In the SendErrorResponse(Socket clientSocket) method of CreateResponse.cs, you pass null as the second parameter, which is for the byte[] byteContent parameter. Then, the first thing you do in SendResponse(Socket clientSocket, byte[] byteContent, string responseCode, string contentType) is call byteContent.Length. That will throw an exception because it is null.

An obvious easy fix is to simply pass an empty byte array.
GeneralRe: Bug In CreateResponse.cs Pin
Pritesh Dhokchaule13-May-14 17:32
Pritesh Dhokchaule13-May-14 17:32 
AnswerDownload link Pin
Pritesh Dhokchaule5-May-14 4:52
Pritesh Dhokchaule5-May-14 4:52 
QuestionDownload link? Pin
descartes5-May-14 4:29
descartes5-May-14 4:29 

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.