A mechanism for serving HTTP requests in C#

This is part 4 of the sequel to "How to build a web based user interaction layer in C#". In this post, I will describe a mechanism that we can use to locate resources to process HTTP requests in C#.

In part 1, we learn how to receive HTTP requests from clients in our C# program using the System.Net.HttpListener.

In part 2, we learn how we can process the HTTP request using the facilities provided by the System.Net.HttpListenerRequest class.

In part 3, we learn how we can prepare a HTTP response using the facilities provided by the System.Net.HttpListenerResponse class to send back to the client.

Visualize the needed components in the object oriented way

We can identify three main components which will work together to serve HTTP requests from the client.

1) The HTTP server

We can use the HttpListener to build our HTTP server. The HTTP server will run its own thread and serve as the role of listening for the HTTP request. Whenever a HttpListenerContext instance is received, it will call the resource locator to find the corresponding request handler to serve the HTTP request.

2) The resource locator

The resource locator will examine the HTTP request to determine the resource that the client wish to submit the HTTP request to. If the resource cannot be found, the resource locator response with a 404. If the resource is found, the resource locator create a new thread and call the corresponding request handler to process the HTTP request.

3) The request handlers

The request handlers are instances of the HttpRequestHandler interface. They will process HTTP requests that are directed to them and create the corresponding HTTP response back to the client.

A code example

Suppose we want to create a C# program that can greet the user through the browser.

When I type the url http://localhost:12345/Morning?name=Clivant in my browser address bar, I will see the message Good morning Clivant!.

When I type the url http://localhost:12345/Afternoon?name=Clivant in my browser address bar, I will see the message Good afternoon Clivant!.

When I left out my name, I will see the message Good morning stranger! and Good afternoon stranger! respectively.

When I type any other url, I will see the message Could not locate HTTP resource. or the ugly 404 page when my browser is an Internet Explorer.

The HttpRequestHandler interface

We first define the HttpRequestHandler interface to serve as a contract between the resource locator and the request handlers.

public interface HttpRequestHandler
{
    void Handle(HttpListenerContext context);

    string GetName();

} // end public interface HttpRequestHandler

The Handle method will allow the request handlers to examine the HTTP request and respond to the client. The GetName method returns the name of the request handler so that the HttpResourceLocator can locate it.

Three implementations of the HttpRequestHandler interface

This one is for the cases when the resource locator cannot find the a request handler to handle a HTTP request.

using System.Net;
using System.Text;

public class InvalidHttpRequestHandler : HttpRequestHandler
{
    public const string NAME = "/InvalidWebRequestHandler";

    public void Handle(HttpListenerContext context)
    {
        HttpListenerResponse serverResponse = context.Response;

        // Indicate the failure as a 404 not found
        serverResponse.StatusCode = (int) HttpStatusCode.NotFound;

        // Fill in the response body
        string message = "Could not find resource.";
        byte[] messageBytes = Encoding.Default.GetBytes(message);
        serverResponse.OutputStream.Write(messageBytes, 0, messageBytes.Length);

        // Send the HTTP response to the client
        serverResponse.Close();

        // Print a message to console indicate invalid request as well
        Console.WriteLine("Invalid request from client. Request string: "
            + context.Request.RawUrl);
    } // end public void handle(HttpListenerContext context)

    public string GetName()
    {
        return NAME;
    } // end public string GetName()

} // end public class InvalidHttpRequestHandler

This one is for greeting good mornings.

using System.Net;
using System.Text;

public class MorningHttpRequestHandler : HttpRequestHandler
{
    public const string NAME = "/Morning";

    public void Handle(HttpListenerContext context)
    {

        HttpListenerResponse response = context.Response;
        response.StatusCode = (int)HttpStatusCode.OK;

        // Get name from query string
        string name = context.Request.QueryString["name"];
        string message;
        if (name == null)
        {
            message = "Good morning stranger!";
        }
        else
        {
            message = "Good morning " + name + "!";
        } // end if

        // Fill in response body
        byte[] messageBytes = Encoding.Default.GetBytes(message);
        response.OutputStream.Write(messageBytes, 0, messageBytes.Length);
        // Send the HTTP response to the client
        response.Close();

    } // end public void Handle(HttpListenerContext context)

    public string GetName()
    {
        return NAME;
    }
} // end public class MorningHttpRequestHandler

And this one is for greeting good afternoons.

using System.Net;
using System.Text;

public class AfternoonHttpRequestHandler : HttpRequestHandler
{
    public const string NAME = "/Afternoon";

    public void Handle(HttpListenerContext context)
    {

        HttpListenerResponse response = context.Response;
        response.StatusCode = (int)HttpStatusCode.OK;

        // Get name from query string
        string name = context.Request.QueryString["name"];
        string message;
        if (name == null)
        {
            message = "Good afternoon stranger!";
        }
        else
        {
            message = "Good afternoon " + name + "!";
        } // end if

        // Fill in response body
        byte[] messageBytes = Encoding.Default.GetBytes(message);
        response.OutputStream.Write(messageBytes, 0, messageBytes.Length);
        // Send the HTTP response to the client
        response.Close();

    } // end public void Handle(HttpListenerContext context)

    public string GetName()
    {
        return NAME;
    } // end public string GetName()

} // end public class AfternoonHttpRequestHandler

The resource locator

using System.Collections.Generic;
using System.Net;
using System.Threading;

public class HttpResourceLocator
{
    private Dictionary<string, HttpRequestHandler> _httpRequestHandlers;

    public HttpResourceLocator()
    {
        _httpRequestHandlers = new Dictionary<string, HttpRequestHandler>();
        // Add the default handler that will handle invalid web request
        this.AddHttpRequestHandler(new InvalidHttpRequestHandler());

    } // end private HttpRequestController()

    public void AddHttpRequestHandler(HttpRequestHandler httpRequestHandler)
    {
        // If the httpRequestHandler is not yet added
        if (!_httpRequestHandlers.ContainsKey(httpRequestHandler.GetName()))
        {
            // Add a new record
            _httpRequestHandlers.Add(httpRequestHandler.GetName(), httpRequestHandler);
        }
        else
        {
            // Replace it
            _httpRequestHandlers[httpRequestHandler.GetName()] = httpRequestHandler;
        }
    } // end public void AddHttpRequestHandler(HttpRequestHandler httpRequestHandler)

    public void HandleContext(HttpListenerContext listenerContext) {

        // Search for the requested handler
        HttpListenerRequest request = listenerContext.Request;
        // Use the absolute path of the url to find the request
        // handler
        string requestHandlerName = request.Url.AbsolutePath;

        // Find the request handler to handle the request

        HttpRequestHandler handler;
        // If request handler is found
        if (_httpRequestHandlers.ContainsKey(requestHandlerName))
        {
            // Get the corresponding request handler
            handler = _httpRequestHandlers[requestHandlerName];
        }
        else {
            // Use the InvalidHttpRequestHandler to handle the request
            handler = _httpRequestHandlers[InvalidHttpRequestHandler.NAME];
        } // end if

        this.InvokeHandler(handler, listenerContext);

    } // end public void handleContext(HttpListenerContext listenerContext)

    private void InvokeHandler(HttpRequestHandler handler,
        HttpListenerContext context)
    {
        // Start a new thread to invoke the handler to process the HTTP request
        HandleHttpRequestCommand handleHttpRequestCommand
            = new HandleHttpRequestCommand(handler, context);
        Thread handleHttpRequestThread = new Thread(handleHttpRequestCommand.Execute);
        handleHttpRequestThread.Start();
    } // end private void InvokeHandler(HttpRequestHandler handler,
      //         HttpListenerContext context)

    // Helper class for invoking handler to process
    // HTTP request
    public class HandleHttpRequestCommand
    {
        private HttpRequestHandler _handler;
        private HttpListenerContext _context;

        public HandleHttpRequestCommand(HttpRequestHandler handler,
            HttpListenerContext context)
        {
            this._handler = handler;
            this._context = context;
        }

        public void Execute()
        {
            this._handler.Handle(this._context);
        }
    } // end public class HandleHttpRequestCommand

} // end public class HttpResourceLocator

The resource locator utilises a System.Collections.Generic.Dictionary instance to contain the different request handlers, which can be added via the AddHttpRequestHandler method. When a HTTP request arrives via the HandleContext method, the resource locator will invoke the corresponding request handler in a new thread.

The HTTP server

public class HttpServer : IDisposable
{
    private HttpListener _httpListener = null;
    private Thread _connectionThread = null;
    private Boolean _running, _disposed;

    private HttpResourceLocator _resourceLocator = null;

    public HttpServer(string prefix)
    {
        if (!HttpListener.IsSupported) {
            // Requires at least a Windows XP with Service Pack 2
            throw new NotSupportedException(
                "The Http Server cannot run on this operating system.");
        } // end if HttpListener is not supported

        _httpListener = new HttpListener();
        // Add the prefixes to listen to
        _httpListener.Prefixes.Add(prefix);

        _resourceLocator = new HttpResourceLocator();

    } // end WebServer()

    public void AddHttpRequestHandler(HttpRequestHandler requestHandler)
    {
        _resourceLocator.AddHttpRequestHandler(requestHandler);
    }

    public void Start()
    {
        if (!_httpListener.IsListening)
        {
            _httpListener.Start();
            _running = true;
            // Use a thread to listen to the Http requests
            _connectionThread = new Thread(new ThreadStart(this.ConnectionThreadStart));
            _connectionThread.Start();
        } // end if httpListener is not listening

    } // end public void start()

    public void Stop()
    {
        if (_httpListener.IsListening)
        {
            _running = false;
            _httpListener.Stop();
        } // end if httpListener is listening
    } // end public void stop()

    // Action body for _connectionThread
    private void ConnectionThreadStart()
    {
        try
        {
            while (_running)
            {
                // Grab the context and pass it to the HttpResourceLocator to handle it
                HttpListenerContext context = _httpListener.GetContext();
                _resourceLocator.HandleContext(context);

            } // while running
        }
        catch (HttpListenerException)
        {
            // This will occurs when the listener gets shutdown.
            Console.WriteLine("HTTP server was shut down.");
        } // end try-catch

    } // end private void connectionThreadStart()

    public virtual void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    } // end public virtual void Dispose()

    private void Dispose(bool disposing)
    {
        if (this._disposed)
        {
            return;
        }
        if (disposing)
        {
            if (this._running)
            {
                this.Stop();
            }
            if (this._connectionThread != null)
            {
                this._connectionThread.Abort();
                this._connectionThread = null;
            }
        }
        this._disposed = true;
    } // private void Dispose(bool disposing)

} // end class HttpServer

The HttpServer accepts one Uniform Resource Identifier (URI) prefix when instantiated. It utilises an instance of the System.Net.HttpListener class internally to listen for HTTP requests that clients send to the URI prefix. Whenever a HTTP request is received, it delegates the handling of the HTTP request to the resource locator. It provides a AddHttpRequestHandler method that will add an instance of the HttpRequestHandler to the resource locator.

A executable implementation

The Program class concludes our code example with the creation of an executable that listens for HTTP requests that is send to port 12345.

using System;

public class Program
{
    public static void Main(string[] args)
    {

        HttpServer server = new HttpServer("http://*:12345/");
        // Add the HttpRequestHandlers
        server.AddHttpRequestHandler(new MorningHttpRequestHandler());
        server.AddHttpRequestHandler(new AfternoonHttpRequestHandler());
        // Start the server
        server.Start();
        Console.ReadKey();

    } // end
} // end public class Program

Go build your own web based interaction layer in C#

At this point, I hope I had covered enough details to aid you in building your own web based interaction layer in C#. There are areas which I did not cover, in particular, security of the web based interaction layer. If you need security, I strongly recommend IIS, Apache or any other web servers that are time-tested.

Related posts

The following is a list of posts that relates to sending HTTP requests to web servers. Feel free to look through them as well. 🙂

Need data persistence for your C# web server?

You could probably look at my experience with System.Data.SQLite in C#.

Advertisements

About Clivant

Clivant a.k.a Chai Heng enjoys composing software and building systems to serve people. He owns techcoil.com and hopes that whatever he had written and built so far had benefited people.

4 Comments

  • Boban
    December 26, 2011 at 5:40 am

    Thank you so much…This is what i have searching for. I think this is the best tutorials I have ever seen. I’ll be grateful all of my life on you. Thanks again 🙂

    • clivant
      December 26, 2011 at 9:16 am

      Glad it helped! You are welcomed. 🙂

  • Sarah
    August 2, 2013 at 1:55 am

    You saved my time 2days 🙂 Thank you very much!

    • Clivant
      August 5, 2013 at 6:00 pm

      You are welcomed! 🙂

Advertisements
Advertisements