Simplest Embedded Web Server Ever with HttpListener

While working on solving a CR_Documentor known issue, I realized I needed to have some sort of embedded web server running so I could serve up dynamically generated content. I didn't need a full ASP.NET stack, just something I could pipe a string to and have it serve that up so a hosted IE control would be getting content from a "live server" rather than from a file on disk or from in-memory DOM manipulation... because both of those latter methods cause security warnings to pop up.

I looked at creating a dependency on the ASP.NET development server, WebDev.WebServer.exe, or the assembly that contains the guts of that, WebDev.WebHost.dll, but both of those only get installed in certain configurations of Visual Studio (I think it's when you install the Visual Web Developer portion) and I couldn't really assume everyone had that. Paulo Morgado then pointed me to HttpListener, and let me tell you, that's a pretty sweet solution.

Here's a very simple web server implementation that uses HttpListener. You provide it a content string (the "Content" property) and that's what the response content is. It doesn't read files from the filesystem, it doesn't do auth, it doesn't do ASP.NET... it's just the simplest of simple servers, which is exactly what I need for CR_Documentor.

using System;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.Threading;

namespace HttpListenerExample
{
  public class WebServer : IDisposable
  {
    private const string Prefix = "http://*:11235/";
    private bool _disposed = false;
    private HttpListener _listener;
    private IAsyncResult _cycleResult = null;

    public string Content { get; set; }

    public bool AcceptingRequests { get; private set; }

    public WebServer()
    {
      if (!HttpListener.IsSupported)
      {
        throw new NotSupportedException("Your platform does not support HttpListener.");
      }
      this.Content = "Content has not yet been set.";
      this._listener = new HttpListener();
      this._listener.Prefixes.Add(Prefix);
      this.AcceptingRequests = false;
    }

    public void Start()
    {
      if (this.AcceptingRequests)
      {
        return;
      }
      this._listener.Start();
      ThreadStart cycle = new ThreadStart(this.AsyncListenThreadStart);
      this._cycleResult = cycle.BeginInvoke(null, null);
      this.AcceptingRequests = true;
      Debug.WriteLine("Listener now accepting requests.");
    }

    public void Stop()
    {
      if (!this.AcceptingRequests)
      {
        return;
      }
      this.AcceptingRequests = false;
      this._listener.Stop();
      Debug.WriteLine("Listener no longer accepting requests.");
    }

    private void AsyncListenThreadStart()
    {
      while (this.AcceptingRequests)
      {
        try
        {
          HttpListenerContext context = this._listener.GetContext();
          HttpListenerRequest request = context.Request;
          HttpListenerResponse response = context.Response;
          Debug.WriteLine(String.Format("[{0}] Received request.", DateTime.Now.Ticks));
          string responseContent = String.Format("<html><head><title>Listener: {0}</title></head><body><p>{1}</p></body></html>", DateTime.Now.Ticks, this.Content);
          byte[] buffer = Encoding.UTF8.GetBytes(responseContent);
          response.ContentLength64 = buffer.Length;
          response.OutputStream.Write(buffer, 0, buffer.Length);
          response.OutputStream.Close();
          Debug.WriteLine(String.Format("[{0}] Sent response.", DateTime.Now.Ticks));
        }
        catch (HttpListenerException err)
        {
          Debug.WriteLine(String.Format("ERROR: {0}", err.Message));
          break;
        }
      }
    }

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

    private void Dispose(bool disposing)
    {
      if (this._disposed)
      {
        return;
      }
      if (disposing)
      {
        Debug.WriteLine("Disposing of listener managed resources.");
        this.Stop();
        this._listener = null;
      }
      this._disposed = true;
    }

    ~WebServer()
    {
      this.Dispose(false);
    }
  }
}

With this simple wrapper, you can new-up a web server instance, start it listening for requests, set the Content property with the content you want, and hit http://localhost:11235/ to get the content in your browser. Dispose the instance and you're done. Here's what it looks like in a simple console program host:

using System;

namespace HttpListenerExample
{
  class Program
  {
    static void Main(string[] args)
    {
      using (WebServer listener = new WebServer())
      {
        listener.Start();
        Console.WriteLine("Listener accepting requests: {0}", listener.AcceptingRequests);
        Console.WriteLine("Setting content.");
        listener.Content = "Content set by console app.";
        Console.WriteLine("Press any key to exit.");
        Console.ReadLine();
      }
      Console.ReadLine();
    }
  }
}

I'm going to be using this approach in CR_Documentor to serve up the content preview and maybe augment it a bit later so I can also serve images from embedded resources and such, making the preview that much richer and more accurate.