{"id":100,"date":"2019-02-13T13:02:55","date_gmt":"2019-02-13T12:02:55","guid":{"rendered":"https:\/\/informedica.nl\/?p=100"},"modified":"2020-11-15T09:53:34","modified_gmt":"2020-11-15T08:53:34","slug":"testing-a-saturn-web-app","status":"publish","type":"post","link":"https:\/\/informedica.nl\/?p=100","title":{"rendered":"Testing a Saturn Web App"},"content":{"rendered":"\n<p>The first thing I will trying when using a new framework or library is to start writing code in a script file and see what happens in the interactive. Normally, I am just to stupid to understand the guidelines, but by experimenting and finding it out myself by writing code I manage to get my head around the new framework or library I am using. <\/p>\n\n\n<p><!--more--><\/p>\n\n\n<p>The same applies when I tried to get a web app running using <a href=\"https:\/\/saturnframework.org\/\">Saturn<\/a>. Previously, I used <a href=\"https:\/\/saturnframework.org\/\">Suave<\/a> and then <a href=\"https:\/\/github.com\/giraffe-fsharp\/Giraffe\">Giraffe <\/a>as primary web server solutions. Saturn is actually, yet another shell around the <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/?view=aspnetcore-2.2\">ASP.NET<\/a> core framework, actually Giraffe wraps ASP.NET Core and Saturn wraps Giraffe. So, I had some previous experience with similar frameworks. Still, getting a web app up and running handling requests is a daunting task (to me it is).<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Requests and Responses<\/h4>\n\n\n\n<p>The whole notion of a web app is that there is a client that fires requests at a server, which responds with &#8230; responds. In order to keep things simple I want my webserver to have one channel for dynamic requests: api\/request, using a POST method to post the actual request. Upon which the server responds with an option response (things can go wrong). I therefore have the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"fsharp\" class=\"language-fsharp\">namespace Shared\r\n\r\nmodule Request =\r\n\r\n    module Configuration =\r\n\r\n        type Msg = Get\r\n\r\n    module Patient =\r\n\r\n        type Msg =\r\n            | Init\r\n            | Clear\r\n            | Get\r\n            | Year of int\r\n            | Month of int\r\n            | Weight of float\r\n            | Height of float\r\n\r\n    module AcuteList =\r\n        type Msg = Get of Shared.Models.Patient.Patient\r\n\r\n    type Msg =\r\n        | PatientMsg of Patient.Msg\r\n        | AcuteListMsg of AcuteList.Msg\r\n        | ConfigMsg of Configuration.Msg\r<\/code><\/pre>\n\n\n\n<p>First of all the code is shared, so the request type can be used by both the client and the server (using the <a href=\"https:\/\/safe-stack.github.io\/\">SAFE-stack<\/a> setup). Thus the client can sent a request the server also understands. Secondly I wrap the actually request in the request type. This enables me to have one type that represents patient requests or configuration requests, etc&#8230; The whole thing looks like a:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>Request &#8211;&gt; Response function<\/p><\/blockquote>\n\n\n\n<p>Next the response looks very similar.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"fsharp\" class=\"language-fsharp\">namespace Shared\r\n\r\nmodule Response =\r\n\r\n    module Configuration =\r\n        type Settings = Setting list\r\n        and Setting =\r\n            { Department : string\r\n              MinAge : int\r\n              MaxAge : int\r\n              MinWeight : float\r\n              MaxWeight : float }\r\n\r\n    type Response =\r\n        | Configuration of Configuration.Settings\r\n        | Patient of Shared.Models.Patient.Patient\r\n\r\n    open Configuration\r\n\r\n    let createSettings dep mina maxa minw maxw =\r\n        {\r\n            Department = dep\r\n            MinAge = mina\r\n            MaxAge = maxa\r\n            MinWeight = minw\r\n            MaxWeight = maxw\r\n        }<\/code><\/pre>\n\n\n\n<p>Again the code is shared by server and client, so the server can create a response the client understands. And now I want to start playing around with this concept. <\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Running the code in a script file<\/h4>\n\n\n\n<p>The first thing you need to do is to run the code in a script file. In a previous <a href=\"https:\/\/informedica.nl\/?p=92\">post<\/a> I described how to load all the required libraries in the script file. The caveat being that sometimes the referenced libraries are in the wrong order. The same applies here, so I had to manually move the following libs down to the end of the load script file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"fsharp\" class=\"language-fsharp\">#r \"C:\\\\Users\\\\foo\\\\.nuget\\\\packages\\\\giraffe\\\\3.5.1\\\\lib\\\\net461\\\\Giraffe.dll\"\r\n#r \"C:\\\\Users\\\\foo\\\\.nuget\\\\packages\\\\thoth.json.giraffe\\\\1.1.0\\\\lib\\\\netstandard2.0\\\\Thoth.Json.Giraffe.dll\" \r\n#r \"C:\\\\Users\\\\foo\\\\.nuget\\\\packages\\\\saturn\\\\0.8.0\\\\lib\\\\netstandard2.0\\\\Saturn.dll\" <\/code><\/pre>\n\n\n\n<p>I also make sure that the path is set to the current source code path so relative paths can be resolved to the correct absolute paths.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"fsharp\" class=\"language-fsharp\">Environment.CurrentDirectory &lt;- Path.Combine( __SOURCE_DIRECTORY__, \".\/..\/\")<\/code><\/pre>\n\n\n\n<p>My script file resides in a sub folder of the actually server code, hence the &#8220;..\/..\/&#8221;. <\/p>\n\n\n\n<p>I then can create the actual server. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"fsharp\" class=\"language-fsharp\">module Server =\r\n\r\n\r\n    let dataPath = Path.GetFullPath \".\/..\/..\/data\/config\/genpres.config.json\"\r\n\r\n\r\n    let processRequest (req : Request.Msg) =\r\n        match req with\r\n        | Request.ConfigMsg msg ->\r\n            match msg with\r\n            | Request.Configuration.Get ->\r\n                Path.GetFullPath dataPath\r\n                |> File.ReadAllText\r\n                |> Decode.Auto.unsafeFromString&lt;Shared.Response.Response>\r\n                |> Some\r\n        | Request.PatientMsg msg ->\r\n            match msg with\r\n            | Request.Patient.Init ->\r\n                Shared.Models.Patient.patient\r\n                |> Shared.Response.Patient  \r\n                |> Some\r\n            | _ -> None\r\n        \r\n        | _ -> None\r\n\r\n\r\n    let tryGetEnv =\r\n        System.Environment.GetEnvironmentVariable\r\n        >> function\r\n        | null\r\n        | \"\" -> None\r\n        | x -> Some x\r\n\r\n    let publicPath = Path.GetFullPath \"..\/Client\/public\"\r\n\r\n    let port =\r\n        \"SERVER_PORT\"\r\n        |> tryGetEnv\r\n        |> Option.map uint16\r\n        |> Option.defaultValue 8085us\r\n\r\n    let getInitCounter() : Task&lt;Counter> = task { return { Value = 42 } }\r\n\r\n\r\n    let webApp =\r\n        router\r\n            {\r\n            get \"\/api\/init\" (fun next ctx -> task { let! counter = getInitCounter()\r\n                                                    return! json counter next ctx }) \r\n            post \"\/api\/request\" (fun next ctx ->\r\n                task {\r\n                    let! resp = task {\r\n                        let! req = ctx.BindJsonAsync&lt;Shared.Request.Msg>()\r\n                        return req |> processRequest }\r\n                    return! json resp next ctx }) }\r\n\r\n    let configureSerialization (services : IServiceCollection) =\r\n        services.AddSingleton&lt;Giraffe.Serialization.Json.IJsonSerializer>\r\n            (Thoth.Json.Giraffe.ThothSerializer())\r\n\r\n    let app =\r\n        application {\r\n            url (\"http:\/\/0.0.0.0:\" + port.ToString() + \"\/\")\r\n            use_router webApp\r\n            memory_cache\r\n            use_static publicPath\r\n            service_config configureSerialization\r\n            use_gzip\r\n        }<\/code><\/pre>\n\n\n\n<p>This code handles the retrieval of the request:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"fsharp\" class=\"language-fsharp\">            post \"\/api\/request\" (fun next ctx ->\r\n                task {\r\n                    let! resp = task {\r\n                        let! req = ctx.BindJsonAsync&lt;Shared.Request.Msg>()\r\n                        return req |> processRequest }\r\n                    return! json resp next ctx }) }\r<\/code><\/pre>\n\n\n\n<p>This code handles the processing of the request and the generation of the response:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">    let processRequest (req : Request.Msg) =\r\n        match req with\r\n        | Request.ConfigMsg msg ->\r\n            match msg with\r\n            | Request.Configuration.Get ->\r\n                Path.GetFullPath dataPath\r\n                |> File.ReadAllText\r\n                |> Decode.Auto.unsafeFromString&lt;Shared.Response.Response>\r\n                |> Some\r\n        | Request.PatientMsg msg ->\r\n            match msg with\r\n            | Request.Patient.Init ->\r\n                Shared.Models.Patient.patient\r\n                |> Shared.Response.Patient  \r\n                |> Some\r\n            | _ -> None\r\n        \r\n        | _ -> None<\/code><\/pre>\n\n\n\n<p>Fairly simple isn&#8217;t it. But then I want to start playing around with this setup. Therefore, I need to start the server as a separate process and be able to stop the server once I am done.  Simply running: run Server.app, as normal won&#8217;t work because then the server is running in process and I cannot start &#8216;talking&#8217; to the server by running code in the FSI. <\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Starting up and stopping a web server in a separate thread<\/h4>\n\n\n\n<p>Luckily, F# has an elegant solution to enable code to run in a separate process called MailboxProcessors. So, you can start the webserver in a MailboxProcessor, or Agent and then run code to use the webserver. The one thing is that if you stop the MailboxProcessor, the webserver still keeps running until you restart the FSI. Therefore, you need to start the webserver with a <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.threading.cancellationtoken?view=netframework-4.7.2\">CancellationToken<\/a>, like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"fsharp\" class=\"language-fsharp\">module Application =\r\n    open Microsoft.AspNetCore.Hosting\r\n\r\n    \/\/\/Runs Saturn application with the ability to stop the server\r\n    let start (app: IWebHostBuilder) token =\r\n        app.Build().RunAsync(token) |> ignore\r<\/code><\/pre>\n\n\n\n<p>Finally I have to create and be able to start and stop the webserver.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"fsharp\" class=\"language-fsharp\">module Test =\r\n\r\n    type WebServerMsg = Start | Stop\r\n\r\n    let createWebServer () =\r\n        let source = new CancellationTokenSource()\r\n\r\n        MailboxProcessor.Start &lt;| fun inbox ->\r\n            let rec loop (source : CancellationTokenSource) =\r\n                async {\r\n                    let! msg = inbox.Receive()\r\n                    match msg with\r\n                    | Start ->\r\n                        printfn \"Starting the webserver\"\r\n                        Application.start Server.app source.Token\r\n                        return! loop source\r\n                    | Stop ->\r\n                        printfn \"Stopping the webserver\"\r\n                        source.Cancel ()\r\n                        return ()\r\n                }\r\n            loop source\r\n\r\n\r\n    let server = createWebServer ()\r\n\r\n\r\n    type Method = GET | POST\r\n\r\n    let fetchUrl method json url =\r\n        let encode json = Encode.Auto.toString(0, json, false)\r\n        let req = WebRequest.Create(Uri(url))\r\n\r\n        req.ContentType &lt;- \"application\/json\"\r\n        req.Method &lt;- match method with | GET -> \"GET\" | POST -> \"POST\"\r\n\r\n        match json with\r\n        | Some json ->\r\n\r\n            use streamWriter = new StreamWriter(req.GetRequestStream())\r\n            let s = json |> encode\r\n\r\n            streamWriter.Write(s)\r\n            streamWriter.Flush()\r\n            streamWriter.Close()\r\n        | None -> ()\r\n        \r\n        use resp = req.GetResponse()\r\n        \r\n        use stream = resp.GetResponseStream() \r\n        use reader = new IO.StreamReader(stream)\r\n        try\r\n            let html = reader.ReadToEnd()\r\n            printfn \"finished downloading %s\" url \r\n            html \r\n\r\n        with\r\n        | e ->\r\n            printfn \"error: %s\" e.Message\r\n            \"\"<\/code><\/pre>\n\n\n\n<p>Now I can write some code to test my setup:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"fsharp\" class=\"language-fsharp\">open Test\r\n\r\nStart\r\n|> server.Post\r\n\r\n\"http:\/\/localhost:8085\/api\/request\"\r\n|> fetchUrl POST (Request.Configuration.Get |> Request.ConfigMsg |> Some)\r\n|> Decode.Auto.unsafeFromString&lt;Shared.Response.Response Option>\r\n\r\nStop\r\n|> server.Post\r<\/code><\/pre>\n\n\n\n<p>The one thing that is not working (yet) is the ability to restart the server. Once it is stop, you need to reset the FSI and reload again. So, if anybody has a solution? Meanwhile this is a perfect way to play with a webserver.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The first thing I will trying when using a new framework or library is to start writing code in a script file and see what happens in the interactive. Normally, I am just to stupid to understand the guidelines, but by experimenting and finding it out myself by writing code I manage to get my &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/informedica.nl\/?p=100\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Testing a Saturn Web App&#8221;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9],"tags":[],"class_list":["post-100","post","type-post","status-publish","format-standard","hentry","category-programming"],"_links":{"self":[{"href":"https:\/\/informedica.nl\/index.php?rest_route=\/wp\/v2\/posts\/100","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/informedica.nl\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/informedica.nl\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/informedica.nl\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/informedica.nl\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=100"}],"version-history":[{"count":8,"href":"https:\/\/informedica.nl\/index.php?rest_route=\/wp\/v2\/posts\/100\/revisions"}],"predecessor-version":[{"id":185,"href":"https:\/\/informedica.nl\/index.php?rest_route=\/wp\/v2\/posts\/100\/revisions\/185"}],"wp:attachment":[{"href":"https:\/\/informedica.nl\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=100"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/informedica.nl\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=100"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/informedica.nl\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=100"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}