Event-driven HTTP server in C # using Rx and HttpListener

Original author: José F. Romaniello
  • Transfer
Big enough name? Yes? In this post, I will show you an alternative approach in creating a simple event-oriented HTTP server in C #, using the power of Reactive Extensions .

Introduction

I am not very good at explaining, so I will quote a very interesting article from Dan York about the event model node.js:
The “traditional” mode of web servers has always been based on a stream model. When you start Apache or any other web server, it starts accepting connections. When it accepts a connection, it keeps the connection open until it finishes processing the page or another transaction. If reading a page from disk or writing results to a database takes several microseconds, then the web server is blocked for input / output operations. (This is referred to as “blocking I / O”). To scale this type of server, you will need to run additional copies of the server itself (referred to as “thread-based”, since each copy usually requires an additional operating system thread).
In contrast, Node.JS uses an event-driven model in which the web server accepts requests, quickly puts them to processing, and then takes them for the next request. When the initial request is completed, it returns to the processing queue and when it reaches the end of the queue, the results are returned back (or everything that requires the next action is performed). This model is very efficient and scalable, because the web server usually always accepts requests, because does not wait for a single read or write operation to complete. (This method is called as “non-blocking I / O” or “event-oriented I / O”).

What is going on in the .NET world?

Many things happen around this in the .NET ecosystem:

Alternative approach

Using the HttpListener class and Reactive Extensions, we can create something like this:
public class HttpServer : IObservable, IDisposable
{
  private readonly HttpListener listener;
  private readonly IObservable stream;
  public HttpServer(string url)
  {
    listener = new HttpListener();
    listener.Prefixes.Add(url);
    listener.Start();
    stream = ObservableHttpContext();
  }
  private IObservable ObservableHttpContext()
  {
    return Observable.Create(obs =>
              Observable.FromAsyncPattern(listener.BeginGetContext,
                                       listener.EndGetContext)()
                   .Select(c => new RequestContext(c.Request, c.Response))
                   .Subscribe(obs))
             .Repeat()
             .Retry()
             .Publish()
             .RefCount();
  }
  public void Dispose()
  {
    listener.Stop();
  }
  public IDisposable Subscribe(IObserver observer)
  {
    return stream.Subscribe(observer);
  }
}

Some notes on this code:
  • FromAsyncPattern is a convenient method that ships with Rx. This method converts Begin / End signatures to IObservable
  • RequestContext is a lightweight wrapper for working with HttpListener. I am not going to give its code here, however, you can see the entire source code a bit later.
  • I repeat: if you have ever seen the use of HttpListener, then I am sure you saw the code inside the while loop. It is the same.
  • Try again: if we get an error, then try again.
  • Publish / Refcount: this will help us create “warm” observers from “cold” ones. They behave like "hot." You can read more here and here .

Usage example

You can create any type of web application based on this concept. A hello world application will look like this:
static void Main()
{
    //a stream os messages
    var subject = new Subject();
    using(var server = new HttpServer("http://*:5555/"))
    {
      var handler = server.Where(ctx => ctx.Request.Url.EndsWith("/hello"))
         .Subscribe(ctx => ctx.Respond(new StringResponse("world")));
      Console.ReadLine();
      handler.Dispose();
    }
}

I recommend that all you do is asynchronous. For example, if you connect to a database, then this should be an asynchronous operation, and you will need to hold callbacks / observables / Tasks, etc. together.

There is an even more interesting application that I would like to share, which is called long polling :
Long polling is a variation of the traditional polling technique and allows you to emulate sending information from the server to the client. With long polling, the client requests information from the server in the same manner as with a normal request. However, if the server does not have any information available for the client, then instead of sending an empty response, the server holds the request and waits for the information to be available.

So, here is the simplest example of long polling working through the above code:
class Program
{
  static void Main()
  {
    //a stream os messages
    var subject = new Subject();
    using(var server = new HttpServer("http://*:5555/"))
    {
      //the listeners stream and subscription
      var listeners = server
          .Where(ctx => ctx.Request.HttpMethod == "GET")
          .Subscribe(ctx => subject.Take(1) //wait the next message to end the request
                       .Subscribe(m => ctx.Respond(new StringResponse(m))));
      //the publishing stream and subscrition
      var publisher = server
        .Where(ctx => ctx.Request.HttpMethod == "POST")
        .Subscribe(ctx => ctx.Request.InputStream.ReadBytes(ctx.Request.ContentLength)
                   .Subscribe(bts =>
                     {
                      ctx.Respond(new EmptyResponse(201));
                      subject.OnNext(Encoding.UTF8.GetString(bts));
                     }));
      Console.ReadLine();
      listeners.Dispose();
      publisher.Dispose();
    }
  }
}

As you can see, we are making observers work ... There is no blocking operation. Even reading from a stream is an asynchronous operation.

Want to see working code?

Below is a video demonstrating the operation of the code: And, in the end, the source code is published here under opensource if you want to delve into it step by step or just study it. Special thanks to Gustavo Machado, Silvio Massari and the guys from the Nancy framework for the tips and part of the code that I stole from them.
image



Also popular now: