【问题标题】:F# asynchronous HTTP request - parse JSON responseF# 异步 HTTP 请求 - 解析 JSON 响应
【发布时间】:2017-12-27 15:56:19
【问题描述】:

我正在尝试使用F# 的以下 sn-p 向 Firebase 发送异步 POST 请求:特别是以下允许匿名登录的操作:

https://firebase.google.com/docs/reference/rest/auth/#section-sign-in-anonymously

    let makePostRequest (url : string) (requestBody : string) = 
    let req = WebRequest.CreateHttp url
    req.CookieContainer <- new CookieContainer()
    req.Method <- "POST"
    req.ProtocolVersion <- HttpVersion.Version10
    let postBytes = requestBody |> System.Text.Encoding.ASCII.GetBytes
    req.ContentLength <- postBytes.LongLength
    req.ContentType <- "application/xml; charset=utf-8"
    async{
        use! reqStream = req.GetRequestStreamAsync() |> Async.AwaitTask
        do! reqStream.WriteAsync(postBytes, 0, postBytes.Length) |> Async.AwaitIAsyncResult |> Async.Ignore
        reqStream.Close()
        use! res = req.AsyncGetResponse() 
        use stream = res.GetResponseStream()
        use reader = new StreamReader(stream)
        let! rdata = reader.ReadToEndAsync() |> Async.AwaitTask        
        return rdata
    }

我这样称呼它:

    let url = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=XXXXXXXXXXXXXXXXX"
    let data = "returnSecureToken=true"
    makePostRequest url data

我对@9​​87654329@ 很陌生,对它的工作原理有点困惑。如何从该请求中获取 JSON 响应?我怎么知道这何时完成?这会阻塞主线程上的 UI 吗?我正在做的是翻译以下 Swift 代码块:

 Auth.auth().signInAnonymously(completion: { (user, error) in

        if let err = error {
            print(err.localizedDescription)
            return
        }

    })

这里的一切对我来说都很有意义,因为我们有一个完成块,在方法执行完成后会调用它,并且我们知道 user 变量将包含从服务器获得的响应,这对我来说意义不大在 F# 中。

更新:

按照 cmets 中的建议,我参考了 F# Data: HTTP Utilities 并创建了以下代码 sn-p:

let url = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=XXXXXXXXXXXX"
Http.RequestString
    ( url, 
      headers = [ ContentType HttpContentTypes.Json ],
      body = TextRequest """ {"returnSecureToken": true} """)

但是这会产生错误The value, namespace, type or module Http is not defined

更新 2: 我已经解决了以下代码:

let fetchUrl callback url (requestBody : string) =        
    let req = WebRequest.Create(Uri(url)) 
    req.Method <- "POST"
    let postBytes = requestBody |> System.Text.Encoding.ASCII.GetBytes
    req.ContentLength <- postBytes.LongLength
    req.ContentType <- "application/json; charset=utf-8"
    let reqStream = req.GetRequestStream() 
    reqStream.Write(postBytes, 0, postBytes.Length);
    reqStream.Close()

    use resp = req.GetResponse() 
    use stream = resp.GetResponseStream() 
    use reader = new IO.StreamReader(stream) 
    callback reader url

let myCallback (reader:IO.StreamReader) url = 
    let html = reader.ReadToEnd()
    let html1000 = html.Substring(0,1000)
    printfn "Downloaded %s. First 1000 is %s" url html1000
    html      // return all the html


let openLandlord ()  =
    let url = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=XXXXXXXXXXXXXXXXXXX"
    let requestBody = """ {"returnSecureToken": true} """
    fetchUrl myCallback url requestBody

按照这里的例子:

https://fsharpforfunandprofit.com/posts/fvsc-download/

但是,当我在这里调用 openLandlord 函数时:

 override x.ViewDidLoad () =
    base.ViewDidLoad ()
    openLandlord ()

我收到以下错误:

此表达式应为 uint 类型,但此处为 string 类型。

错误:

  1. 在此绑定点或之前的不完整结构化构造。
  2. 构造函数请求的值未定义。
  3. 成员定义中出现意外的关键字匹配。
  4. 未定义值、命名空间、类型或模块响应。
  5. 在成员定义中发现了意外。

【问题讨论】:

  • 你的问题太宽泛了。并且您将一个高级 API 与另一个低级 API 进行比较。如果需要,您可以将请求处理委托给库。例如,FSharp.Data 有很好的 HTTP 实用程序:fsharp.github.io/FSharp.Data/library/Http.html
  • 我觉得不会太宽泛...我要做的就是在不阻塞UI的情况下异步发送一个HTTP POST请求,然后在一个闭包块中获取这个请求的JSON响应(或与之等效的东西)。我尝试使用 Http.fs 但这显然不会为 xamarinios 安装
  • 抱歉,我只是读到“对它的工作原理有点困惑”,因为它鼓励我详细解释代码,这需要太宽泛的答案。如果您只想从响应数据中获取 JSON,则可以使用来自 FSharp.Data 或 .NET Newtonsoft.JSON 的 F# 特定 JSON 类型提供程序。两者都有很多例子。如果您使用异步操作,UI 线程会很好。
  • 不用担心@AlexAtNet,你能看看编辑吗?我尝试按照您链接的 FSharp.Data 中的 POST 示例进行操作
  • 您可以使用 AsyncRequestString 异步获取字符串。要将 Http 导入作用域,您可以在代码文件的开头使用“open FSharp.Data”(类似于 Swift 中的导入)。

标签: swift rest asynchronous f# http-post


【解决方案1】:

检查以下代码:

open System
open System.Net
open FSharp.Data

type Response = JsonProvider<""" { "name":"John", "age":94 } """>

[<EntryPoint>]
let main argv =
  let request () =
    async {
      let url = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=XXXXXXXXXXXX"
      return! Http.AsyncRequestString
        ( url,
          headers = [ "Content-Type", HttpContentTypes.Json ],
          body = TextRequest """ {"returnSecureToken": true} """ )
    } |>  Async.Catch
  let result = request ()
               |> Async.RunSynchronously
  match result with
  | Choice1Of2 text -> let json = Response.Parse text
                       printfn "name: %A" json.Name   
  | Choice2Of2 e -> printfn "request failed: %A" e
  0 // return an integer exit code

如果类型提供不适用于您的设置,这可能会有所帮助:https://github.com/Microsoft/visualfsharp/issues/3303

将示例 JSON 替换为服务的预期内容。

【讨论】:

  • 我知道,问题是我无法让您的代码在我必须构建它的 iOS 设置中工作,此外我对 F# 非常陌生并且不太了解代码中的控制流以及如何使用它,我不想简单地复制粘贴一些我无法理解的东西......而该代码更简单,我理解它是如何工作的。
  • 我认为type 语句应该从let 中移出一级。
  • 是的,有帮助,问题是我仍然无法使用 FSharp.Data... 即使我通过数据包管理器添加了它并添加了 open 语句,它似乎没有被识别。有没有办法在不使用 FSharp.Data 的情况下做到这一点?
  • 您是否尝试添加 fsc.props 以启用类型提供程序?您也可以使用 e Newtonsoft.Json 代替,这是 .net 中 JSON 的事实标准。
  • @AlexNetkachov,看来,您在格式化字符串后漏掉了一个逗号:printfn "name: %A" json.Name
猜你喜欢
  • 2016-04-14
  • 1970-01-01
  • 1970-01-01
  • 2018-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多