On the bleeding edge – Akka.Net + Suave.io + DNX in F#

So after playing with Suave.io, I wanted to add some new bleeding edge stuff to it. 😉 And run it on a beta environment, the DNX 🙂

First, the complete source of it is here: https://github.com/SabotageAndi/SuaveAkkaCore

Thanks to Alxandr there is F# support for the DNX: https://github.com/YoloDev/YoloDev.Dnx.FSharp

So here is a quick go through of the code to get the stuff working:

1.) Add needed NuGet feeds

YoloDev: https://www.myget.org/F/yolodev/api/v2″
AspNetVNext: https://www.myget.org/F/aspnetvnext/api/v2″

2.) Adapt project.json for F# Support:

{
“version”: “1.0.0-beta-*”,
“dependencies”: {
“YoloDev.Dnx.FSharp”: { “type”: “build”, “version”: “1.0.0-beta-*” },
   “Suave”: “0.29.1”,
        “Akka”: “1.0.3”,
        “Akka.FSharp”: “1.0.3”
},
“frameworks”: {
“dnx451”: {
“frameworkAssemblies”: {
“System.Runtime”: “”,
“System.Threading.Tasks”: “”
}
}
},
“compiler”: {
“name”: “F#”,
    “compilerAssembly”: “YoloDev.Dnx.FSharp”,
    “compilerType”: “YoloDev.Dnx.FSharp.FSharpProjectCompiler”
}
}

The bold text are the changes needed for the F# support, the italic one are the needed changes for Akka.net and Suave.io.

 

3.) initialize Akka.net & Suave.io

 member x.Main () =
    use akkaSystem = System.create "SuaveAkkaCore" (Configuration.defaultConfig()) //1
  
    spawn akkaSystem "root" (actorOf2 handleRequest) |> ignore //2
  
    let cts = new CancellationTokenSource()
    
    let startingServer, shutdownServer = startWebServerAsync defaultConfig (app akkaSystem) //3
    Async.Start(shutdownServer, cts.Token)
    
    startingServer |> Async.RunSynchronously |> printfn "started: %A"

    printfn "Press Enter to stop"
    Console.Read() |> ignore

    
    cts.Cancel()

on //1 the Akka System is created with a standard configuration. It is named “SuaveAkkaCore”. This is later important to find the actor again.
The only one actor (named root) in this example is spawnd on //2. When the actor gets a message, the handleRequest function is called.
Last part on initializing the system is on //3 the start of suave. Here is only one webpart involved.

4.) Suave webpart

 let app system : WebPart = 
      fun (httpContext : HttpContext) ->
          async {
              let response = sendRequestToActor system httpContext
              return! response
          }

It’s a simple web part that reacts on every request and calls the sendRequestToActor method with the current Akka system and HttpContext (there is the request, response stuff in Suave).

5.) sending message to actor

let sendRequestToActor system (httpContext : HttpContext) =
    
    let callActor = async { 
      let actor = select "akka://SuaveAkkaCore/user/root" system
      let! resp = actor <? httpContext
      return Some resp
    }
   
    let response = callActor |> Async.RunSynchronously
    response httpContext   

In the first part, the root- Actor is selected. Here is the system name and actor name needed.
With the <? operator, the actor is asked with the HttpContext.
After that, we run the async workflow and return the response.

6.) the actor function itself

 let handleRequest (mailbox : Actor<'a>) (msg : HttpContext) =
    let url = msg.request.url.ToString()                                     
    mailbox.Sender() <! (OK url)

In the actor function, we get the mailbox and the message as parameters. Here I simply take the request url and return it as OK response.

To run the stuff, you need the DNX runtime installed.
Switch to the mono runtime for dnx with “dnvm use 1.0.0-beta6-12120 -p -r mono”
With a “dnu restore” you get the needed packages.
After that, a “dnx . run” starts the program.

Now browse to http://localhost:8083 and you see as response the browsed url.

Have fun!

Playing with AngularJS and Suave.IO

After I found Suave.IO (lightweight HTTP Server in F#) I wanted to try it out. As I also wanted to play with AngularJS (I know I am a little bit late, but normally I get out of the way of web development). So why not combine both and create a small webserver that hosts the web app and the webservices.

As example for the AngularJS app, I used the PhoneCat Tutorial App from AngularJS.

You find the complete source on GitHub here: AngularSuave Repository

Explanation step-by-step:

let mimeTypes =
    defaultMimeTypesMap
        >=> (function | ".json" -> mkMimeType "application/json" true | _ -> None)

In the default mime types from Suave is no entry for json- Files. So I simply add it.

let rootPath = Path.GetFullPath "../../../Web"

The source files for the AngularJS are located in a separate project.

let webConfig = 
    { 
        defaultConfig with 
            homeFolder = Some rootPath
            mimeTypesMap = mimeTypes
    }

I use the default server config that is shipped with Suave and only adjust the needed parts.

let getPhone phoneName =
    printfn "getting data for phone: %s" phoneName

    let phoneFolder = Path.Combine(rootPath, "app", "phones")
    browseFile phoneFolder phoneName

Function to return the data for the phones. It is stored as json- Files.

let api =
    choose
        [
            GET >>= choose 
                [
                    pathScan "/api/phones/%s" (fun s -> getPhone s)
                ]
        ]

In Suave you have WebParts which can react on the different urls. Here I react on all calls to /api/phones/ and call the function to get the phone data.

let getFile name =
    let rootPath = webConfig.homeFolder.Value
    browseFile rootPath name

Function to return the file content of the requested file.

let angularApp =
    choose
        [ GET >>= choose
            [
                path "/" >>=  redirect "app/index.html"
                pathScan "/%s" (fun s -> getFile s)                
            ]
        ]

The AngularJS app is still located under /app/*. So I make a redirect, when you simple browse to the root page.
For all other request to /*, I simple return the requested file.

let app =
    choose
        [
            api
            angularApp
        ]

The choose web part executes every web part in the list and only stops if someone is successful.
In this case, the correct order is important, because the /%s in the angularApp WebPart will eat every request.
If the order is changed, no call is ever made to the api Webpart.

[<EntryPoint>]
let main argv = 
    
    let cts = new CancellationTokenSource()
    let startingServer, shutdownServer = startWebServerAsync webConfig app

    Async.Start(shutdownServer, cts.Token)

    startingServer |> Async.RunSynchronously |> printfn "started: %A"

    printfn "Press Enter to stop"
    Console.Read() |> ignore

    cts.Cancel()

    0

At the end, I simply start the the webserver with the individual web config and the app WebPart.

When you start the programm and open http://localhost:8083/ you can see the PhoneCat Tutorial App hosted by Suave.IO.

So have fun with it. 😉