A Web Server In Less Than 100 Lines of Code


Last night at the Lincoln .NET User’s Group, we had our second laptop meeting. A few people brought some code, a dev tip, or a fully working program to demo. I choose to show my "100 Line Web Server" I created to perform some unit testing on an app that was HTTP-centric, lately.

So, here is the entire program. I hope it’s useful to some and at least interesting to most.

Program.cs:

[more]

using System;
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Collections.Generic;

namespace MicroWebServer
{
  /// <summary>
  /// A web server in a console app; implemented in less than 100 lines of C# code
  /// </summary>
  /// <remarks>
  /// Note: See the application settings for port number and physical file
  /// path properties.
  /// Limitations: 
  /// Only files listed in the WebServer._MimeTypes dictionary will be served properly
  /// Only files with extensions will be served (not a bad thing, just take note)
  /// Only files within the root or first sub-folder will be served
  /// </remarks>
  class Program
  {
    [ServiceContract]
    private interface IWebServerContract
    {
      [OperationContract]
      [WebInvoke(UriTemplate = "{file}.{extension}", Method="GET")]
      Stream FileRequest(string file, string extension);
      [OperationContract]
      [WebInvoke(UriTemplate = "{folder}/{file}.{extension}", Method = "GET")]
      Stream SubFolderFileRequest(string folder, string file, string extension);
    }

    private class WebServer : IWebServerContract
    {
      private Dictionary<string, string> _MimeTypes;

      public WebServer()
      {
        this._MimeTypes = new Dictionary<string, string>();
        this._MimeTypes.Add(string.Empty, "text/plain");
        this._MimeTypes.Add("htm", "text/html");
        this._MimeTypes.Add("txt", "text/plain");
        this._MimeTypes.Add("gif", "image/gif");
        this._MimeTypes.Add("jpg", "image/jpeg");
      }
      public Stream FileRequest(string file, string extension)
      {
        string filePath = file;
        if (!string.IsNullOrEmpty(extension))
        filePath += "." + extension;

        Log(string.Format("Request for {0} received", filePath));
        string fullPath = Path.Combine(Properties.Settings.Default.WebRoot, filePath);
        if (File.Exists(fullPath) && !filePath.Contains(".."))
        {
          string mimeType = GetMimeType(extension);
          Log(string.Format("Serving {0} as {1}", fullPath, mimeType));
          WebOperationContext.Current.OutgoingResponse.ContentType = mimeType;
          return File.OpenRead(fullPath);
        }
        else
        {
          Log("File does not exist or access denied.");
          return null;
        }
      }
      public Stream SubFolderFileRequest(string folder, string file, string extension)
      {
        return FileRequest(string.Format("{0}\\{1}", folder, file), extension);
      }
      private string GetMimeType(string extension)
      {
        return (this._MimeTypes.ContainsKey(extension)) ? this._MimeTypes[extension] : this._MimeTypes[string.Empty];
      }
    }

    static void Main(string[] args)
    {
      WebServiceHost host = new WebServiceHost(typeof(WebServer), new Uri(string.Format("http://localhost:{0}/", Properties.Settings.Default.PortNumber)));
      WebHttpBinding binding = new WebHttpBinding();
      host.AddServiceEndpoint(typeof(IWebServerContract), binding, string.Empty);

      Log(string.Format("Starting web server for {0}", Properties.Settings.Default.WebRoot));
      host.Open();
      Log(string.Format("Web server started at {0}", host.BaseAddresses[0]));

      Console.WriteLine("You may press [Enter] to close this window");
      Console.ReadLine();
      host.Close();
    }
    private static void Log(string message)
    {
      Console.WriteLine(string.Format("{0}\t{1}", DateTime.Now, message));
    }
  }
}

app.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name="MicroWebServer.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
        </sectionGroup>
    </configSections>
    <userSettings>
        <MicroWebServer.Properties.Settings>
            <setting name="WebRoot" serializeAs="String">
                <value>C:\WebRoot</value>
            </setting>
            <setting name="PortNumber" serializeAs="String">
                <value>8888</value>
            </setting>
        </MicroWebServer.Properties.Settings>
    </userSettings>
</configuration>