不理解 Suave API,总是返回相同的结果

not understanding the Suave API, always the same result is returned

这是一个测试:

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

在这个例子中,getServerTime 函数被调用一次,就是这样,每次对端点的后续调用都将 return 原始结果。

我不明白为什么?当我使用带参数的 pathScan 时,每次都会调用该函数,正如预期的那样,但在这种情况下,使用简单的 get,只有第一次调用被完成,而 this 被定义为一个函数。

但我也完全不理解文档(从文档的流程、内容和整体文档结构...),所以答案是可能很简单:)

首先,我强烈建议您学习单子组合。这是理解这些东西的必要基础。它会让您了解 >=>>>= 是什么以及如何处理它们。

至于手头的问题:是的,您将 getServerTime 定义为一个函数,但这无关紧要,因为该函数在 [=17= 的构造过程中只被调用一次]值。

服务器的结构实际上是一个函数 HttpContext -> Async<HttpContext option>。它获得一个请求上下文和 returns 它的修改版本。所有这些组合器 - choose>=> 等等 - 它们都使用这样的函数。

表达式path "/servertime"也是这样的函数。字面上地。你可以这样称呼它:

let httpCtx = ...
let newCtxAsync = path "/servertime" httpCtx

此外,表达式getServerTime()也是这样的函数。所以你可以这样做:

let httpCtx = ...
let newCtxAsync = getServerTime () httpCtx

这就是 WebPart 类型。这是一个从上下文到新上下文的异步函数。

现在,>=>运算符所做的就是组合这些功能。使它们通过管道将上下文从一个 Web 部件传递到下一个 Web 部件。就这些了。


当您编写 getServerTime 函数时,您创建了一个 WebPart 始终 return 相同的东西。有点像这样:

let f x y = printf "x = %d" x
let g = f 42

在这里,g 是一个函数(就像 WebPart 是一个函数),但无论何时调用它,它总是 return "x = 42"。为什么?因为我部分应用了该参数,所以它现在有点“融入”g 的定义中。当前时间在您在 getServerTime.

中创建的 WebPart 中“烘焙”的方式相同

如果您希望每次都return编辑不同的时间,您需要每次都重新创建 WebPart。在每次调用时构造一个 new WebPart,其中包含该调用的时间。要做到这一点的最小更改可能是:

let private getServerTime () : WebPart =
    let time : WebPart = fun ctx -> (DateTime.UtcNow |> string |> Successful.OK) ctx
    time >=> setMimeType "text/plain"

从表面上看,time 的定义可能很愚蠢:毕竟,let f x = g x 总是可以被 let f = g 替换,对吧?好吧,并非总是如此。只要 g 是纯的。但是你这里的webpart不是:要看当前时间。

这样,每次 time webpart 是“运行”(这意味着它获取一个上下文作为参数)时,它将 运行 DateTime.UtcNow,然后将其传递给 Successful.OK,然后将上下文传递给结果函数。