ASP.Net Web API made it fairly easy to create a self-hosted HTTP server. However, the process of taking that self-hosted server and deploying it was an exercise that was left to the reader. This series of blog posts documents the refinements that I have made to my process of creating and deploying self-hosted web servers. This process is not limited to ASP.Net Web API. Any Owin compatible HTTP application will work with this method.
The elevator pitch
The high level overview of this process is that I am using the TopShelf OSS project to build applications that can either run as console applications or as a Windows Service. I have extended the Topshelf service to integrate an Owin compatible web host that then can host an Owin compatible web application. I then package this up as a Chocolatey nuget with some scripts to initiate the service installation and deploy to a public nuget feed. The end result is I can go to a clean server and using a single command to install a new service instance or update an existing one.
Why would I want to self-host?
Before we did into the details of the process, let us first review why someone might want to self-host a web Api or web site. If you are already convinced of the value, then feel free to skip ahead.
The vast majority of web sites and web apis that are run on the Windows platform are hosted by IIS. Over the years IIS has become somewhat of a kitchen sink product that tries to solve every problem for every scenario. Whether you are running a web farm in giant enterprise company, or trying to deploy a one person blog, IIS tries to meet every need. The end result is that it is massively complex. In order to try and address that complexity there is are lots of default behaviours. Some features are opt-in, some features are opt-out. In the end you either get lucky and your site just works, or you have to start learning about the intricacies of how to configure IIS to behave exactly as you want. This is further complicated by the fact that IIS has gone through a fair number of revisions and stuff keeps changing. It also can be tricky when you are trying to host multiple sites, because you need to ensure that one site’s config doesn’t conflict with another site’s config. This is all possible. It just takes knowledge.
Self-host is a very different approach. Self-host allows you to run an HTTP server within your application’s own process. You could host a server within you own desktop application, you can host a server within a console application, or you can create a Windows Service to host the HTTP server. A self-host HTTP Server is usually a very bare bones HTTP server. Not at all like IIS. If you want logging, you are going to have to set it up yourself. If you want output caching, same thing. Obviously this has its pro’s and con’s. For some scenarios it makes way more sense to use IIS, however, if you need a lightweight, highly isolated HTTP Server that needs almost no config to get up and running, the self-host might just be what you need.
One challenge with self-host is that there is no “out-of the box” infrastructure to get up and running. This post will demonstrate a way to build and deploy HTTP servers that are hosted in a Windows Service.
Why a Windows Service?
A Windows Service is the most natural way to run a HTTP Server. Usually a HTTP Server is running all of the time. With a Windows Service you configure it to startup when the system starts up, you can define what credentials the service runs under, you can define specific actions to take if the process fails. A individual service runs in its own process, so you get process isolation mechanism similar to what IIS application pools provide.
Topshelf is the easiest way to create a Windows Service
You can create a Windows Service by using the “Windows Service” Visual Studio template which effectively creates a Console application and as service class that derives from serviceBase. This isn’t a terrible way to do it, but you will find that if you try running that project interactively from Visual Studio it will complain about not being able to start a Windows service this way. This is a pain when you want debug your api. Having install the service and then start and stop it on every build is annoying. There are a number of workarounds to this, however, by far the best solution I have found is an open source library called TopShelf.
Topshelf provides a variety of helper functionality around managing you Windows Service. Not only does it provide a programmatic interface but it also gives you and out-of-the-box command line interface for managing services. If you have ever spent time hunting for the InstallUtil.exe program then you will appreciate being able to do things like this:
myservice.exe install
One way to create a TopShelf based service is to create a class that implements the ServiceControl interface. This is very simple interface:
public interface ServiceControl
{
bool Start(HostControl hostControl);
bool Stop(HostControl hostControl);
}
In order to get the service running, you need to pass a setup lambda to a static method. Like this,
HostFactory.Run(x => { Do some setup stuff } );
The setup stuff is really not that hard, but I’m not going to go into the details of this because the service class I created hides the setup goo. I will discuss this service class once we have introduced one more piece of the technology puzzle.
Who is this Owin chap?
For a HTTP Server to be useful, it needs to actually host some kind of HTTP application. OWIN is a specification that defines an interface between HTTP servers and HTTP applications. This means that if our HTTP Server host supports the OWIN interface then all different kinds of web applications can be hosted. For example, our host can support ASP.NET Web API, ASP.NET MVC, Nancy, SimpleWeb, FubuMVC, SignalR, etc.
OwinServiceHost
The OwinServiceHost class is the class I created that uses Topshelf to create a Windows Service, and use OwinHttpListener to host any OWIN application. OwinHttpListener is a Microsoft implementation of an OWIN compatible host based on the .Net framework class HttpListener which itself is a thin wrapper around the HTTP.SYS kernel mode driver that is the same driver used by IIS. So, at the core, we are using the same code that sends and receives HTTP requests in IIS, we just dumped all the extra baggage. In theory I could have used any other OWIN compatible host, but I know this one works and has good performance. Changing this class to support other hosts would be fairly straightforward.
The OwinServiceHost class does perform a few important additional functions. When the service is first installed, we ask the user what URL the HTTP application will live at. The current implementation only accepts a single URL and it has to be an exact match to the URL that we want to serve requests to. That means if you use http://127.0.0.1/ don’t expect to get a response at http://localhost/. There are pro’s and con’s to using HTTP.SYS. A benefit is that you get a very sophisticated URL sharing mechanism that allows you to host multiple HTTP Servers on the same port, even along side existing IIS installations. The downside is that you need administrator rights to do the reserve the URL for you application. Also the syntax of the URL reservation mechanism is particularly picky.
The OwinServiceHost class takes care of doing the URL reservation when you install the service and removing the reservation when you uninstall it. Once the user has entered the URL, the OwinServiceHost class stores this in the service’s config file. If you edit this config file to change the URL after the service has been installed, then you need to make sure that you restart the service for the changes to take effect.
To make it easy to re-use the OwinServiceHost class in multiple projects, I have packaged it up as a nuget Tavis.OwinService. To get a simple example working, just create a new Console application and install the package,
Install-package Tavis.OwinService
and then a very basic example would look like this,
class Program
{
static void Main(string[] args)
{
var service = new OwinServiceHost(new Uri("http://localhost:1002/"), simpleApp)
{
ServiceName = "SampleService",
ServiceDisplayName = "Sample Service",
ServiceDescription = "A sample service"
};
service.Initialize();
}
private static appFunc simpleApp = (env) =>
{
var sw = new StreamWriter((Stream) env["owin.ResponseBody"]);
var headers = (IDictionary<string, string[]>) env["owin.ResponseHeaders"];
var content = "Hello World";
headers["Content-Length"] = new string[] {content.Length.ToString()};
headers["Content-Type"] = new string[] {"text/plain"};
var task = sw.WriteAsync(content);
sw.Flush();
return task;
};
}
The OwinServiceHost class takes two arguments, a default URL and an “appFunc”. The appFunc is the OWIN interface to any HTTP application. For more details on the “simpleApp” that I am using here, see this post. The URL that is passed to the constructor of the class is a default hosting URL. During the process of installing the service, the user will be prompted to enter an alternative URL.
Once this console application is built, it can be run just as a regular console application, or it can be installed as a service using the Topshelf command line commands.
SampleService.exe install
SampleService.exe start
SampleService.exe stop
SampleService.exe uninstall
If you would like to see the sample application and the source for the OwinServiceHost, it is all available on GitHub here. If you want to try and build your own service, the Nuget package here.
In the next installment of this blog series, we will talk about how to deploy this service using Chocolatey Nugets!
Image credit: Grain elevator https://flic.kr/p/hED9mA
Image credit: Chocolate https://flic.kr/p/4pq4rk