Running Suave in ASP.NET Core (and on top of Kestrel)

All text and code copyright (c) 2016 by Dustin Moris Gorski. Used with permission.

Original post dated 2016-12-15 available at https://dusted.codes/running-suave-in-aspnet-core-and-on-top-of-kestrel

By Dustin Moris Gorski

Ho ho ho, happy F# Advent my friends! This is my blog post for the F# Advent Calendar in English 2016. First a quick thanks to Yan Cui who has pointed out this calendar to me last year and a big thanks to Sergey Tihon who is organising this blogging event and was kind enough to reserve me a spot this year.

santa-suave

In this blog post I wanted to write about two technologies which I am particularly excited about: Suave and ASP.NET Core. Both are frameworks for building web applications, both are written in .NET and both are open source and yet they are very different. Suave is a lightweight web server written entirely in F# and belongs to the family of micro frameworks similar to NancyFx. ASP.NET Core is Microsoft's new cloud optimised web framework which has been built from the ground up on top of .NET Core and all of its goodness. Both are fairly new cutting edge technologies and both are extremely fun to work with.

What I like the most about Suave is that it's written in F# for F#. It is really well designed and embraces functional concepts like railway oriented programming in its core architecture. Lately I've been a big fan of functional programming and being able to build web applications in a functional way is not only very productive but also a heap of fun. ASP.NET Core is object oriented and closer related to C#, but nonetheless an extraodinary new web framework. After more than 14 years of developing (the old) ASP.NET stack Microsoft has completely revamped the platform and built something new which is extremely fast and flexible. I love Kestrel, I love how ASP.NET Core is completely modular and extendable (via middleware) and I love how it is cross platform compatible and supported by Microsoft (Mono you have served us well but I am glad to move on now). There's more than one good reason to go with either framework and that's why I really wanted to combine them together.

Ideally I would like to continue building web applications with Suave in F# and then plug them into the ASP.NET Core pipeline to run them on top of Kestrel and benefit from both worlds.

Suave inside ASP.NET Core in theory

In order to better understand Suave let's have a quick look at a simple web application:

open System
open Suave
open Suave.Successful
open Suave.Operators
open Suave.RequestErrors
open Suave.Filters

let simpleApp =
    choose [
        GET >=> choose [
            path "/" >=> OK "Hello world from Suave."
            path "/ping" >=> OK "pong"
        ]
        NOT_FOUND "404 - Not found."
    ]

[<EntryPoint>]
let main argv = 
    startWebServer defaultConfig simpleApp
    0

Even in this simple example you can clearly see the core concept behind Suave. An application is always an assembly of one or many web parts. A WebPart is a function which takes a HttpContext and returns an option of HttpContext wrapped in an async workflow. Through combinators such as choose or >=> (and many others) one can compose a complex web application with routing, model binding, view engines and anything else that someone might want to do. At the end there is one top level function of type WebPart which takes in a HttpContext and returns a HttpContext. In this example this function is called simpleApp.

In theory the one thing required to plug a Suave web app into ASP.NET Core would be to take an incoming HTTP request from ASP.NET Core and convert it into an HttpContext in Suave, execute the top level web part, and then translate the resulting HttpContext back into an ASP.NET Core response:

suave-in-aspnetcore-concept

The other thing which you get with Suave is a self hosted web server which is built into the framework and the traditional way of starting a Suave web application. The startWebServer function takes a SuaveConfig object and the top level WebPart as input parameters. The config object allows web server specific configuration such as HTTP bindings, request quotas, timeout limits and many more things to be set.

When putting a Suave app into ASP.NET Core then it would be running on a different web server under the hood (Kestrel by default) and it wouldn't necessarily make sense to use an existing SuaveConfig in this scenario. Considering that ASP.NET Core offers other natural ways of configuring server settings, I think it is fair to skip the SuaveConfig when merging Suave into ASP.NET Core and mainly focusing on a smooth WebPart integration.

Suave inside ASP.NET Core in practice

Taking theory into practise I thought I can make it happen, and when a programmer says that then it usually means to google for an existing solution first. I was lucky to discover Suave.Kestrel which is a super early alpha version of the above concept written by Krzysztof Cieslak. Krzysztof is the developer behind the Ionide project which makes F# development in Visual Studio Code even possible, and therefore a massive thanks to him and his great contributions as well!

Even though this project was a good start to begin with there was still loads of work left to do. First I started off by using the existing code and trying to extend it, but then I quickly realised that I was fighting more with the tools than writing any code, which lead me to the decision of creating an entirely new project written in C#. Why C#? Because the Visual Studio tooling for F# projects in .NET Core is non existent (at least at the moment). As much as I love F# if I cannot properly debug or reason with my code then I rather switch to C# and get the job done.

However as a seasoned C# developer that was not a big problem and in the end it wouldn't even matter if the library was written in C#, F# or VB.NET as long as it would allow an easy integration from an F# point of view. Moments like this make me really appreciate the flexibility of the .NET framework.

Introducing Suave.AspNetCore

After my initial start on the project it took me another 3 months (mainly because I had absolutely no time) to finally release the first version of Suave.AspNetCore and make it available. Since yesterday anyone can install Suave.AspNetCore as a NuGet package for a .NET Core application.

I decided to name the package Suave.AspNetCore because I thought it is a more representative name of what the NuGet package has to offer. While this library makes it perfectly possible to run Suave on top of Kestrel it is certainly not limited to it. Suave.AspNetCore gives a way of plugging a Suave WebPart into the ASP.NET Core pipeline and run it on any environment of someone's desire. In theory Suave can be run alongside NancyFx and ASP.NET Core MVC in the same ASP.NET Core application and let the middleware decide which framework is suited best to satisfy an incoming request.

Current release information

The current version should be able to deal with any incoming web request which can be handled by a Suave WebPart. One thing that is missing (but already in the works) is the support for Suave's web socket implementation.

I shall also note that Suave and F# itself don't have an official stable release for .NET Core yet and therefore the project as a whole should be taken with some caution.

Suave.AspNetCore in action

Ok enough of the talk and let's look at a demo.

First I'll start by using one of my existing F# ASP.NET Core project templates and upgrade it to .NET Core 1.1.

Then I add Suave, Suave.AspNetCore and Newtonsoft.Json to the dependencies:

"dependencies": {
    "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-*",
    "Microsoft.AspNetCore.Diagnostics": "1.1.0",
    "Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
    "Microsoft.Extensions.Logging.Console": "1.1.0",

    "Suave": "2.0.0-*",
    "Suave.AspNetCore": "0.1.0",
    "Newtonsoft.Json": "9.0.1"
  }

Next I move on to the Startup.fs file and create a Suave web application:

module App =
    let catchAll =
        fun (ctx : HttpContext) ->
            let json = 
                JsonConvert.SerializeObject(
                    ctx.request,
                    Formatting.Indented)
            OK json 
            >=> Writers.setMimeType "application/json"
            <| ctx

This app is very basic. You can see that it is a single web part which uses Json.NET to serialize the entire HttpContext and then later returns a successful response of the Json text with a mime type of application/json.

It is not hugely interesting but it is a nice function which will handle every incoming web request and output the resulting HttpContext in a more or less readable way. It's at least a good way of quickly verifying if the incoming web request has been correctly converted into a Suave HttpContext object.

Finally I go to the Startup class and hook up the Suave catchAll web app into the ASP.NET Core pipeline via a middleware:

type Startup() =
    member __.Configure (app : IApplicationBuilder)
                        (env : IHostingEnvironment)
                        (loggerFactory : ILoggerFactory) =
        app.UseSuave(App.catchAll) |> ignore

Save all, dotnet restore, dotnet build and dotnet run:

running-a-suave-aspnetcore-app

If everything is correct then going to http://localhost:5000/ should return a successful response like this:

suave-in-aspnetcore-simple-get-request-result

You can check out the sample app in GitHub and try it yourself!

Differences between vanilla Suave and Suave.AspNetCore

After running a few tests of my own I noticed a few minor differences.

First I noticed that the original Suave web server converts all HTTP headers into lower case values. For example Content-Type: text/html would be stored as content-type: text/html in Suave's HTTP header collection. In contrast ASP.NET Core preserves the original casing. When using the Suave.AspNetCore middleware then it will match the original Suave behaviour, but can be easily overriden by setting the preserveHttpHeaderCasing parameter to true in the UseSuave method:

app.UseSuave(App.catchAll, true) |> ignore

Another difference which I found was that Suave always sets the host variable to 127.0.0.1 for local requests, even when I explicitely call the API with http://localhost:5000/. I wasn't able to find out why or where this is happening and if there is a good reason for it. In this case I didn't align with the original Suave behaviour and kept the values provided by ASP.NET Core.

Other than this I haven't found any big differences and hope that it is (mostly) bug free. The project is open source and I am open for ideas, help or suggestions of any kind.

Merry Christmas everyone!

results matching ""

    No results matching ""