Reputation: 12107
Here is a test:
open System
open System.Threading
open Newtonsoft.Json
open Suave
open Suave.Logging
open Suave.Operators
open Suave.Filters
open Suave.Writers
let private configuration = {
defaultConfig with
bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" 80 ]
}
let private getServerTime () : WebPart =
DateTime.UtcNow |> JsonConvert.SerializeObject |> Successful.OK >=> setMimeType "application/json"
let private webApplication =
choose
[
GET >=> choose
[
path "/servertime" >=> getServerTime ()
]
]
let start () =
let listening, server = startWebServerAsync configuration webApplication
server |> Async.Start
listening |> Async.RunSynchronously |> ignore
[<EntryPoint>]
let main _ =
start()
Thread.Sleep(Timeout.Infinite)
0
with this example, the getServerTime function is called once and this is it, every subsequent call to the endpoint will return the original result.
I don't understand why? When I use pathScan with parameters, then the function is called each time, as expected, but in this case, with a simple get, only the first call is done while this is defined as a function.
But I don't understand the doc at all either (from the flow of the doc, its contents and the overall document structure...), so the answer is probably simple :)
Upvotes: 1
Views: 121
Reputation: 80744
First of all, I would highly recommend that you study monadic composition. This is a necessary foundation for understanding these things. It will give you an idea of what >=>
and >>=
are and how to deal with them.
As for the problem at hand: yes, you defined getServerTime
as a function, but that kind of doesn't matter, because that function is only called once, during construction of the webApplication
value.
The structure of the server is such that it's literally a function HttpContext -> Async<HttpContext option>
. It gets a request context and returns a modified version of it. And all of those combinators - choose
and >=>
and so on - they work with such functions.
The expression path "/servertime"
is also such function. Literally. You can call it like this:
let httpCtx = ...
let newCtxAsync = path "/servertime" httpCtx
Moreover, the expression getServerTime()
is ALSO such function. So you can do:
let httpCtx = ...
let newCtxAsync = getServerTime () httpCtx
That's what the WebPart
type is. It's an async function from context to new context.
Now, what the >=>
operator does is combine these functions. Makes them pipe the context from one webpart to the next. That's all.
When you wrote your getServerTime
function, you created a WebPart
that always returns the same thing. It's kind of like this:
let f x y = printf "x = %d" x
let g = f 42
Here, g
is a function (just like a WebPart
is a function), but whenever it's called, it will always return "x = 42"
. Why? Because I partially applied that parameter, it's sort of "baked in" in the definition of g
now. The same way the current time is "baked in" in the WebPart
that you have created inside getServerTime
.
If you want a different time to be returned every time, you need to recreate the WebPart
every time. Construct a new WebPart
on every call, one with that call's time baked in. The smallest change to do that is probably this:
let private getServerTime () : WebPart =
let time : WebPart = fun ctx -> (DateTime.UtcNow |> string |> Successful.OK) ctx
time >=> setMimeType "text/plain"
On the surface it may look like the definition of time
is silly: after all, let f x = g x
can always be replaced by let f = g
, right? Well, not always. Only as long as g
is pure. But your webpart here is not: it depends on the current time.
This way, every time the time
webpart is "run" (which means it gets a context as a parameter), it will run DateTime.UtcNow
, then pass it to Successful.OK
, and then pass the context to the resulting function.
Upvotes: 3