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. 🙂
- Downloading a file via HTTP post and HTTP get in C#
- Sending a file and some form data via HTTP post in C#
- Uploading large HTTP multipart request with
System.Net.HttpWebRequest
in C# - Handling web server communication feedback with
System.Net.WebException
in C#
Need data persistence for your C# web server?
You could probably look at my experience with System.Data.SQLite in C#.
4 Comments
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 🙂
Glad it helped! You are welcomed. 🙂
You saved my time 2days 🙂 Thank you very much!
You are welcomed! 🙂