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

Build Your Own Web Server

By , 9 Oct 2012
 

Contents

Introduction

We will learn to write a simple web server which can send responses to the most well-known HTTP methods (GET and POST), in C#. Then we will make this server accessible from the internet. This time we will really say "Hello world!"

Simple Web Server

Background

HTTP Protocol

HTTP is a communication protocol between servers and clients. It uses TCP/IP protocol to send/receive requests/responses.

There are a few HTTP methods and we will implement two of them; GET and POST.

GET

What happens when we write an address into address bar of our web browser and hit enter? (We mostly don't specify a port number although it is required for TCP/IP, because it has a default value for http and it is 80. We don't have to specify it if it is 80.)

GET / HTTP/1.1\r\n
Host: atasoyweb.net\r\n
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:14.0) Gecko/20100101 Firefox/14.0.1\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n
Accept-Language: tr-tr,tr;q=0.8,en-us;q=0.5,en;q=0.3\r\n
Accept-Encoding: gzip, deflate\r\n
Connection: keep-alive\r\n\r\n

This is the GET request which is sent by our browser to the server using TCP/IP. This means the browser requests the server to send the contents of "/" from the root folder of "atasoyweb.net".

We (or browsers) can add more headers. But the most simplified version of this request is below:

GET / HTTP/1.1\r\n
Host: atasoyweb.net\r\n\r\n 

POST

POST requests are similar to GET requests. In a GET request, variables are appended to the urls using ? character. But in a POST request, variables are appended to the end of the request after 2 line break characters and total length (content-length) is specified. 

POST /index.html HTTP/1.1\r\n
Host: atasoyweb.net\r\n
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20100101 Firefox/15.0.1\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n
Accept-Language: tr-tr,tr;q=0.8,en-us;q=0.5,en;q=0.3\r\n
Accept-Encoding: gzip, deflate\r\n
Connection: keep-alive\r\n
Referer: http://atasoyweb.net/\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 35\r\n\r\n
variable1=value1&variable2=value2

Simplified version of this request:

POST /index.html HTTP/1.1\r\n
Host: atasoyweb.net\r\n
Content-Length: 35\r\n\r\n
variable1=value1&variable2=value2 

Responses

When a request is received by the server it is parsed and a response with a status code is returned:  

HTTP/1.1 200 OK\r\n
Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)\r\n
Content-Length: {content_length}\r\n
Connection: close\r\n
Content-Type: text/html; charset=UTF-8\r\n\r\n
the content of which length is equal to {content_length}

This is the response header. "200 OK" means everything is OK, requested content will be returned. There are many status codes. We will use just 200, 501 and 404: 

  • "501 Not Implemented": Method is not implemented. We will implement only GET and POST. So, we will send response with this code for all other methods.
  • "404 Not Found": Requested content is not found.

Content Types

Servers must specify the type of the content in their response. There are many content types and these are also called "MIME (Multipurpose Internet Mail Extensions) types" (because they are also used to identify non-ASCII parts of emails). Here are the content types that we will use in our implementation: (you can modify the code and add more) 

  • text/html
  • text/xml
  • text/plain
  • text/css
  • image/png
  • image/gif
  • image/jpg
  • image/jpeg
  • application/zip

If servers specify the wrong content types contents will be misinterpreted. For example, if a server sends plain text using the "image/png" type, the client tries to show the text as an image.

Multithreading

If we want our server to be available even if a response is being sent to an another client at that time, we must create new threads for every request. Thus, every thread handles a single request and exits after it completes its mission. (Multithreading also speeds up page loadings, because if we request a page that uses CSS and includes images, different GET requests are sent for every image and CSS file.)

Implementation of a Simple Web Server

Now we are ready to implement a simple web server. First of all, let's define variables that we will use:

public bool running = false; // Is it running?

private int timeout = 8; // Time limit for data transfers.
private Encoding charEncoder = Encoding.UTF8; // To encode string
private Socket serverSocket; // Our server socket
private string contentPath; // Root path of our contents

// Content types that are supported by our server
// You can add more...
// To see other types: http://www.webmaster-toolkit.com/mime-types.shtml
private Dictionary<string, string> extensions = new Dictionary<string, string>()
{ 
    //{ "extension", "content type" }
    { "htm", "text/html" },
    { "html", "text/html" },
    { "xml", "text/xml" },
    { "txt", "text/plain" },
    { "css", "text/css" },
    { "png", "image/png" },
    { "gif", "image/gif" },
    { "jpg", "image/jpg" },
    { "jpeg", "image/jpeg" },
    { "zip", "application/zip"}
};

Method to start our server: 

public bool start(IPAddress ipAddress, int port, int maxNOfCon, string contentPath)
{
    if (running) return false; // If it is already running, exit.

    try
    {
        // A tcp/ip socket (ipv4)
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
                       ProtocolType.Tcp);
        serverSocket.Bind(new IPEndPoint(ipAddress, port));
        serverSocket.Listen(maxNOfCon);
        serverSocket.ReceiveTimeout = timeout;
        serverSocket.SendTimeout = timeout;
        running = true;
        this.contentPath = contentPath;
    }
    catch { return false; }

    // Our thread that will listen connection requests
    // and create new threads to handle them.
    Thread requestListenerT = new Thread(() =>
    {
        while (running)
        {
            Socket clientSocket;
            try
            {
                clientSocket = serverSocket.Accept();
                // Create new thread to handle the request and continue to listen the socket.
                Thread requestHandler = new Thread(() =>
                {
                    clientSocket.ReceiveTimeout = timeout;
                    clientSocket.SendTimeout = timeout;
                    try { handleTheRequest(clientSocket); }
                    catch
                    {
                        try { clientSocket.Close(); } catch { }
                    }
                });
                requestHandler.Start();
            }
            catch{}
        }
    });
    requestListenerT.Start();

    return true;
}

Method to stop the server:

public void stop()
{
    if (running)
    {
        running = false;
        try { serverSocket.Close(); }
        catch { }
        serverSocket = null;
    }
}

The most important part of the code:

private void handleTheRequest(Socket clientSocket)
{
    byte[] buffer = new byte[10240]; // 10 kb, just in case
    int receivedBCount = clientSocket.Receive(buffer); // Receive the request
    string strReceived = charEncoder.GetString(buffer, 0, receivedBCount);

    // Parse method of the request
    string httpMethod = strReceived.Substring(0, strReceived.IndexOf(" "));

    int start = strReceived.IndexOf(httpMethod) + httpMethod.Length + 1;
    int length = strReceived.LastIndexOf("HTTP") - start - 1;
    string requestedUrl = strReceived.Substring(start, length);

    string requestedFile;
    if (httpMethod.Equals("GET") || httpMethod.Equals("POST"))
        requestedFile = requestedUrl.Split('?')[0];
    else // You can implement other methods...
    {
        notImplemented(clientSocket);
        return;
    }

    requestedFile = requestedFile.Replace("/", @"\").Replace("\\..", "");
    start = requestedFile.LastIndexOf('.') + 1;
    if (start > 0)
    {
        length = requestedFile.Length - start;
        string extension = requestedFile.Substring(start, length);
        if (extensions.ContainsKey(extension)) // Do we support this extension?
            if (File.Exists(contentPath + requestedFile)) //If yes check existence of the file
                // Everything is OK, send requested file with correct content type:
                sendOkResponse(clientSocket,
                  File.ReadAllBytes(contentPath + requestedFile), extensions[extension]);
            else
                notFound(clientSocket); // We don't support this extension.
                                        // We are assuming that it doesn't exist.
    }
    else
    {
        // If file is not specified try to send index.htm or index.html
        // You can add more (default.htm, default.html)
        if (requestedFile.Substring(length - 1, 1) != @"\")
            requestedFile += @"\";
        if (File.Exists(contentPath + requestedFile + "index.htm"))
            sendOkResponse(clientSocket,
              File.ReadAllBytes(contentPath + requestedFile + "\\index.htm"), "text/html");
        else if (File.Exists(contentPath + requestedFile + "index.html"))
            sendOkResponse(clientSocket,
              File.ReadAllBytes(contentPath + requestedFile + "\\index.html"), "text/html");
        else
            notFound(clientSocket);
    }
}

Responses for different status codes:

private void notImplemented(Socket clientSocket)
{
   
    sendResponse(clientSocket, "<html><head><meta 
        http-equiv=\"Content-Type\" content=\"text/html; 
        charset=utf-8\">
        </head><body><h2>Atasoy Simple Web 
        Server</h2><div>501 - Method Not 
        Implemented</div></body></html>", 
        "501 Not Implemented", "text/html");

}

private void notFound(Socket clientSocket)
{
  
    sendResponse(clientSocket, "<html><head><meta 
        http-equiv=\"Content-Type\" content=\"text/html; 
        charset=utf-8\"></head><body><h2>Atasoy Simple Web 
        Server</h2><div>404 - Not 
        Found</div></body></html>", 
        "404 Not Found", "text/html");
}

private void sendOkResponse(Socket clientSocket, byte[] bContent, string contentType)
{
    sendResponse(clientSocket, bContent, "200 OK", contentType);
}

The method that will send responses to clients:

// For strings
private void sendResponse(Socket clientSocket, string strContent, string responseCode,
                          string contentType)
{
    byte[] bContent = charEncoder.GetBytes(strContent);
    sendResponse(clientSocket, bContent, responseCode, contentType);
}

// For byte arrays
private void sendResponse(Socket clientSocket, byte[] bContent, string responseCode,
                          string contentType)
{
    try
    {
        byte[] bHeader = charEncoder.GetBytes(
                            "HTTP/1.1 " + responseCode + "\r\n"
                          + "Server: Atasoy Simple Web Server\r\n"
                          + "Content-Length: " + bContent.Length.ToString() + "\r\n"
                          + "Connection: close\r\n"
                          + "Content-Type: " + contentType + "\r\n\r\n");
        clientSocket.Send(bHeader);
        clientSocket.Send(bContent);
        clientSocket.Close();
    }
    catch { }
}

Usage

// to create new one:
Server server = new Server();
// to start it
server.start(ipAddress, port, maxconnections, contentpath);
// to stop it
server.stop();

Let's Say "Hello" to All The World!

Our simple web server is ready. Now we will make it accessible from the internet. To achieve this, we must redirect requests that come to our modem to our computer. It is simple if our modem supports UPnP.

  1. Download this UPnP Port Forwarder and run it.
  2. Click "Search For Devices" button. If your modem supports UPnP, it will be added to the combobox. 
  3. Click "Update List" button to list forwarded ports.
  4. Then click "Add New" button and fill the form. 
  5. If you check "IP" checkbox and type an IP, only requests from this IP will be redirected. So, do not fill it. 
  6. Internal port must be equal to our server's port.
  7. "Port" and "Internal port" don't have to be equal.

Adding new port forwarding entry

From now on all requests come to "externalip:port" will be redirected from the modem to our computer. To test the server if it is accessible from the internet you can use www.web-sniffer.net. Just write your external IP and port as "http://externalip:port" and hit "Submit" button... 

Test result

History

  • 13 September 2012: The POST method was explained and supported.
  • 3 September 2012: First version.

License

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

About the Author

Huseyin Atasoy
Engineer
Turkey Turkey
Member
I am interested in artifical intelligence, signal processing, electronics, robotics and internet/network technologies.
 
A few of my works:
Real-time face recognition
Real-time facial emotion recognition
LANPhone [Android, Windows]
Javascript encryptor
 
My blog

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   
GeneralMy vote of 5memberHumayun Kabir Mamun10 Apr '13 - 22:20 
Nice Work
Question.JSmemberbedgear8 Mar '13 - 19:23 
Whenever i try to use a site that calls for JS fles, it just hangs, is that because its not set in the program as a file type, or is something this simple not capable of that?
 
But seriously, good stuff. Thumbs Up | :thumbsup:
AnswerRe: .JSmemberHuseyin Atasoy10 Mar '13 - 23:38 
You should add this line to the initializer of the extensions dictionary:
{"js", "application/x-javascript"},
If a file type is not supported, the server returns a "404 Not found" page and continues to wait for other requests.
Thanks.
QuestionSimple WebServer Requestmemberscottie197211 Feb '13 - 8:03 
Hello Mr. Atasoy,
I really like your SimpleWebServer great project.
I am a Visual Basic developer and I was wondering if you could help me convert this project to
Visual Basic? (vs2012) Im have some trouble with the conversions.
 
The main issue is the Extentions Dictionary and Lambda
 

'Warning!!! Lambda constructs are not supported
Dim requestHandler As Thread = New Thread(() => {})
requestHandler.Start()
 
any help would be great.
Thank You.
Scottie
AnswerRe: Simple WebServer RequestmemberHuseyin Atasoy11 Feb '13 - 20:41 
You can use anonymous subs:
Dim requestHandler As New Thread(Sub() 
        clientSocket.ReceiveTimeout = timeout
        clientSocket.SendTimeout = timeout
        Try
            handleTheRequest(clientSocket)
        Catch
            Try : clientSocket.Close() : Catch : End Try
        End Try
    End Sub)
requestHandler.Start()
And:
Private extensions As New Dictionary(Of String, String)
And also you can try this: http://www.developerfusion.com/tools/convert/csharp-to-vb/[^]
GeneralMy vote of 5membersoulprovidergr18 Dec '12 - 3:06 
Great!
GeneralMy vote of 5memberMihai MOGA16 Oct '12 - 3:31 
This is a great inspiring article. I am pretty much pleased with your good work. You put really very helpful information. Keep it up once again.
GeneralRe: My vote of 5memberHuseyin Atasoy16 Oct '12 - 22:24 
Thank you for this nice comment.
GeneralMy vote of 5memberadkalavadia9 Oct '12 - 19:02 
Great work...!!!
GeneralMy vote of 5memberOlivier Giulieri8 Oct '12 - 14:55 
Daring project! Thank you for sharing.
GeneralMy vote of 5memberSachinDakle7 Oct '12 - 22:11 
Amazing and inspiring! Great work, keep it up!Thumbs Up | :thumbsup:
Sachin Dakle

Director

Unicon Computer Technology

Ph:- +91-8390777577

GeneralRe: My vote of 5memberHuseyin Atasoy8 Oct '12 - 0:30 
Thanks.
GeneralMy vote of 5memberkanalbrummer17 Sep '12 - 1:54 
Good article. Shows how simple HTTP works. Useful information for those who want to build HTTP server (or even a client) in embedded environments where you need to do everything on your own and where you have to understand HTTP.
GeneralRe: My vote of 5memberHuseyin Atasoy24 Sep '12 - 21:13 
This is a summary of my purpose Smile | :) . Thanks.
GeneralMy vote of 5memberSanjay_00714 Sep '12 - 11:33 
cool stuff !
GeneralRe: My vote of 5memberHuseyin Atasoy24 Sep '12 - 21:09 
Thank you.
QuestionMy vote of 5memberM I developer14 Sep '12 - 1:22 
Good one
AnswerRe: My vote of 5memberHuseyin Atasoy24 Sep '12 - 21:08 
Thanks.
QuestionNice jobmembersniper_vb12 Sep '12 - 23:36 
Can you support HTTPS in this app?
AnswerRe: Nice jobmemberHuseyin Atasoy13 Sep '12 - 2:15 
In fact, you can get https request using this implementation. (Note that default https port is 443, not 80) But incoming https requests are encrypted. There are certificates, private and public keys... So implementing https requires more work. I don't have sufficient information about ssl...
Questionsupport ASP.NET?memberadriancs12 Sep '12 - 21:11 
is it possible to make it support for .aspx? (asp.net)
AnswerRe: support ASP.NET?memberHuseyin Atasoy13 Sep '12 - 2:04 
I don't know if there is a way to interpret an asp file from c# form app. If you find a method to do so, then you can convert it to a simple asp server.
GeneralMy vote of 5memberSharath279011 Sep '12 - 18:18 
Good!!!!♥♥
GeneralRe: My vote of 5memberHuseyin Atasoy11 Sep '12 - 19:29 
Thanks.
GeneralMy vote of 5memberMonjurul Habib11 Sep '12 - 9:44 
nice work

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.130523.1 | Last Updated 9 Oct 2012
Article Copyright 2012 by Huseyin Atasoy
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid