left-icon

Web Servers Succinctly®
by Marc Clifton

Previous
Chapter

of
A
A
A

CHAPTER 2

Your First Web Server

Your First Web Server


The source code presented in this chapter is in the folder Examples\Chapter 2\Demo in the Bitbucket repository.

Writing a Web Server is Simple

Writing a web server is essentially rather simple. If all we wanted to do is serve up some HTML pages, we could be done with the following implementation.

Namespaces we need to use:

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

Code Listing 1

A couple helpful extension methods:

/// <summary>

/// Some useful string extensions.

/// </summary>

public static class ExtensionMethods

{

  /// <summary>

  /// Return everything to the left of the first occurrence of the specified string,

  /// or the entire source string.

  /// </summary>

  public static string LeftOf(this String src, string s)

  {

    string ret = src;

    int idx = src.IndexOf(s);

    if (idx != -1)

    {

      ret = src.Substring(0, idx);

    }

    return ret;

  }

  /// <summary>

  /// Return everything to the right of the first occurrence of the specified string,

  /// or an empty string.

  /// </summary>

  public static string RightOf(this String src, string s)

  {

    string ret = String.Empty;

    int idx = src.IndexOf(s);

    if (idx != -1)

    {

      ret = src.Substring(idx + s.Length);

    }

  return ret;

}

Code Listing 2

And the program itself:

class Program

{

  static Semaphore sem;

  static void Main(string[] args)

  {

    // Supports 20 simultaneous connections.

    sem = new Semaphore(20, 20);

    HttpListener listener = new HttpListener();

    string url = "http://localhost/";

    listener.Prefixes.Add(url);

    listener.Start();

    Task.Run(() =>

    {

      while (true)

      {

        sem.WaitOne();

        StartConnectionListener(listener);

      }

    });

    Console.WriteLine("Press a key to exit the server.");

    Console.ReadLine();

  }

  /// <summary>

  /// Await connections.

  /// </summary>

  static async void StartConnectionListener(HttpListener listener)

  {

    // Wait for a connection. Return to caller while we wait.

    HttpListenerContext context = await listener.GetContextAsync();

    // Release the semaphore so that another listener can be immediately started up.

    sem.Release();

    // Get the request.

    HttpListenerRequest request = context.Request;

    HttpListenerResponse response = context.Response;

    // Get the path, everything up to the first ? and excluding the leading "/"

    string path = request.RawUrl.LeftOf("?").RightOf("/");

    Console.WriteLine(path); // Nice to see some feedback.

   
    try
    {

    // Load the file and respond with a UTF8 encoded version of it.

    string text = File.ReadAllText(path);

    byte[] data = Encoding.UTF8.GetBytes(text);

    response.ContentType = "text/html";

    response.ContentLength64 = data.Length;

    response.OutputStream.Write(data, 0, data.Length);

    response.ContentEncoding = Encoding.UTF8;

    response.StatusCode = 200; // OK

    response.OutputStream.Close();
    }
    catch(Exception ex)
    {
      Console.WriteLine(ex.Message);
    }

  }

}

Code Listing 3

The previous code initializes 20 listeners. Using semaphores, when a request is received, the semaphore is released and a new listener is created. This code can therefore receive 20 requests simultaneously. We rely on the await mechanism to determine on what thread the continuation (the code after the await) executes. If you are unfamiliar with the use of Task and async/await, Stephan Cleary has an excellent discussion of async/await and execution contexts on his blog at http://blog.stephencleary.com/2012/02/async-and-await.html.

There are two more things we need to do.

First, create an index.html file with the contents:

<p>Hello World</p>

Code Listing 4

The server we just wrote will run within the bin\Debug folder (assuming you haven’t changed the build configuration from “Debug” to “Release”), so we need to put the index.html file into the bin\Debug folder so the application can find it when it tries to load the page associated with the URL.

Solution Tree

Figure 2: Solution Tree

Second, put an icon file named favicon.ico into the bin\Debug folder as well; otherwise, if the browser requests it, the web server will throw a File Not Found exception.

Now, when you run the console app, it will wait for a connection. Fire up your browser and for the URL, and enter:

http://localhost/index.html

Code Listing 5

I am assuming here that you do not have a server already running on port 80 on your machine—if you do, the program will fail.

In the console window you'll see the path emitted, and in the browser you'll see the page rendered as shown in the following figure.

Serving Static Content

Figure 3: Serving Static Content

Issues with localhost?

If your browser is having problems connecting to localhost, edit your C:\Windows\System32\drivers\etc\hosts file and make sure there is an entry that looks like this:

127.0.0.1 localhost

If it's missing, add it, save the file, and reboot the computer.

Writing a Web Server is Complicated!

We created a simple server that serves only a static HTML page, but there are many things wrong with it:

  • Only the HTML MIME type is supported (your browser is rather forgiving—if you get the content type wrong, most of the time it will accommodate the error). Other MIME types[21] include CSS, JavaScript, and of course media, such as images.
  • It doesn't handle the common HTTP methods,[22] namely GET, POST, PUT, and DELETE.
  • We have no exception handling.
  • There's no support for Cross-Site Request Forgery[23] (CSRF) tokens.
  • The server has no concept of session.
  • The server doesn't support HTTPS. SSL/TLS support is critically important in today's world.
  • Some sort of HTML-processing engine would be very useful to resolve connection-specific content on the server before the page is sent to the browser.
  • There is no support for routing requests to, say, a Model-View-Controller[24] (MVC) or Model-View-ViewModel[25] (MVVM) architecture.
  • Our server implementation is entangled with the application-specific HTML pages. We need to decouple it—basically, make it an assembly that our application-specific stuff references.
  • What about master pages?
  • What about authorization, authentication, and session expiration?
  • What about model support?
  • What about integration testing?

Request routing combined with some sort of a controller implementation is really useful when implementing a REST[26] API, something our web server should be able to do as well. REST is also at the center of AJAX[27] and AJAJ[28] requests (SOAP[29] is another common protocol, but REST is much more in vogue nowadays), allowing us to write single-page applications. Here we are implicitly entering into the realm of serving dynamic content. If you're rendering mostly static content, then you could also look at Apache (especially in conjunction with PHP) or Nginx, both of which are primarily static content web servers, but with support for dynamic content.[30]

We Need an Architecture

If you look at a few popular middleware frameworks, such as ASP.NET,[31] Ruby on Rails,[32] or NancyFx[33] (which can run standalone as a server or under IIS as middleware), you'll immediately get a sense that there is a sophisticated architecture supporting the web server. There’s also some very clever built-in functionality that doesn't have anything to do with handling requests, but tends to make the job easier because there's a typical set of common tasks people need to perform when creating a professional website.

If you use any of these frameworks, you will almost immediately notice one or more of the following characteristics:

  • Either enforces or at least defaults to creating a project with an MVC architecture.
  • Has some sort of a view engine for rendering dynamic content at the server before the browser sees the final page, such as ASP.NET's Razor,[34] ASPX view engines, or NancyFx's SuperSimpleViewEngine.[35] Rails supports a wide range of view (also known as "template”) engines.[36]
  • Possibly includes some sort of Object-Relational Mapper (ORM). In the ASP.NET world, this is usually Entity Framework;[37] in Rails we find ActiveRecord.[38]

Underlying these three common features of popular web servers and middleware are three very important premises:

  • You will almost always be rendering dynamic content.
  • The dynamic content will be determined in large part from external data.
  • The Model-View-Controller (MVC) paradigm is the architectural glue that you are going to use for interactions between the user interface and the database.

Note that in the web server implementation presented in this book, the MVC pattern is not baked into the architecture—you are free to use an MVC pattern or not for handling web requests.

Dynamic versus Static Content and the Single-Page Paradigm

The trend (especially as "push servers;” see SignalR[39]) is to move toward single-page applications (SPAs)—the content of the page updates without requiring a full page refresh. A full page refresh requires a callback to the server to load all the content, whereas an SPA requests only the content that it needs.

This makes developing a web application more complicated because you’re not just rendering the page on the server—you’re coding in JavaScript on the client-side to implement the dynamic behavior, and probably using additional JavaScript packages such as jQuery,[40] Knockout,[41] Backbone,[42] Angular,[43] or any number of available options. Furthermore, you’re not just writing “render this page” server-side and client-side code. Instead, a significant portion of what you write on the server will look more like an API to support AJAX/REST callbacks to return the content the client is requesting. In fact, it probably is helpful to think more in terms of writing an API than in terms of writing a website!

But Do We Need All This Overhead?

The simple answer is: no.

The whole reason I have even bothered to write yet another web server from scratch is because those features, which are often integrated with the basic process of a web server and initialized in a new project template, are, while not altogether unnecessary, sometimes better served by a lightweight version of the feature.

The question often comes up of whether to build your own or buy into an existing architecture, and the deeper question, why are we always rewriting prior work?

The answer to both, and the premise of why you're reading this book (other than to learn about the internals of how web servers work) is that, based on the experiences of working with other technologies, you have discovered that your needs are not being met by the existing solutions.

The typical answer, "because the existing technology can be improved upon," is actually a weak argument, especially when one considers that any new technology will have deficiencies in areas other than the technology that it replaces. So, my motivations are to write a web server that not only meets his or her needs but also employs an architecture that does not hinder you from meeting your needs. The premise of such architecture is that the function of a web server should be completely decoupled from paradigms such as MVC, as well as view engine and ORM implementations. These should be in the purview of, if not the application, then at least some middle-tier that you can take or leave depending on your needs.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.