【问题标题】:How to make synchronous url requests with swift 3如何使用 swift 3 进行同步 url 请求
【发布时间】:2016-10-24 12:45:17
【问题描述】:

我知道之前有人问过这个问题,并且我同意大多数声称最好遵循在 Swift 3 中与 URLSession 异步请求的方式。我有以下场景,其中无法使用异步请求。

使用 Swift 3 和在服务器上运行 swift 的能力我遇到了以下问题。

  1. 服务器接收来自客户端的请求
  2. 要处理请求,服务器必须发送一个 url 请求并等待响应到达。
  3. 响应到达后,对其进行处理并回复客户端

问题出现在第 2 步,其中 URLSession 使我们能够仅启动异步数据任务。大多数(如果不是全部)服务器端 Swift Web 框架不支持异步响应。当请求到达服务器时,所有事情都必须同步完成,最后发送响应。

到目前为止,我发现的唯一解决方案是使用 DispatchSemaphore(参见最后的示例),我不确定这是否适用于缩放环境。

任何帮助或想法将不胜感激。

extension URLSession {
    func synchronousDataTaskWithURL(_ url: URL) -> (Data?, URLResponse?, Error?) {
        var data: Data?
        var response: URLResponse?
        var error: Error?

        let sem = DispatchSemaphore(value: 0)

        let task = self.dataTask(with: url as URL, completionHandler: {
            data = $0
            response = $1
            error = $2 as Error?
            sem.signal()
        })

        task.resume()

        let result = sem.wait(timeout: DispatchTime.distantFuture)
        switch result {
        case .success:
            return (data, response, error)
        case .timedOut:
            let error = URLSessionError(kind: URLSessionError.ErrorKind.timeout)
            return (data, response, error)

        }
    }
}

我只有 kitura web 框架的经验,这就是我遇到问题的地方。我想类似的问题存在于所有其他 swift web 框架中。

【问题讨论】:

    标签: swift3 alamofire vapor kitura


    【解决方案1】:

    在 Vapor 中,您可以使用 Droplet 的客户端进行同步请求。

    let res = try drop.client.get("https://httpbin.org")
    print(res)
    

    此外,您可以使用Portal 类使异步任务同步。

    let res = try Portal.open { portal in
        asyncClient.get("https://httpbin.org") { res in
            portal.close(with: res)
        }
    }
    

    【讨论】:

    • 看起来 Vapor 已经解决了这个问题。我更喜欢第二种方法,因为它适用于任何场景(不绑定到特定框架)
    【解决方案2】:

    您的三步问题可以通过使用完成处理程序来解决,即按照 Node.js 约定的回调处理程序:

    import Foundation
    import Kitura
    import HeliumLogger
    import LoggerAPI
    
    let session = URLSession(configuration: URLSessionConfiguration.default)
    
    Log.logger = HeliumLogger()
    
    let router = Router()
    
    router.get("/test") { req, res, next in
        let datatask = session.dataTask(with: URL(string: "http://www.example.com")!) { data, urlResponse, error in
            try! res.send(data: data!).end()
        }
    
        datatask.resume()
    }
    
    Kitura.addHTTPServer(onPort: 3000, with: router)
    Kitura.run()
    

    这是解决问题的快速演示,它绝不遵循最佳 Swift/Kitura 实践。但是,通过使用完成处理程序,我可以让我的 Kitura 应用程序发出 HTTP 调用以获取位于 http://www.example.com 的资源,等待响应,然后将结果发送回我的应用程序的客户端。

    相关 API 的链接:https://developer.apple.com/reference/foundation/urlsession/1410330-datatask

    【讨论】:

    • 从未想过这么简单的解决方案。我的印象是你应该在同一个线程中使用“res”而不是在完成块中。当您说“不是最佳做法”时,您能解释一下原因吗?
    • @zirinisp 我没有包括正确的错误处理或可选的展开 (if let data = data)。对于来自dataTask 的错误和来自res.send 的可能错误(可能重新抛出第二个),您绝对应该在您的生产代码中包含错误处理。
    • 我认为“不是最佳做法”是从块中发送响应。只要这不是问题,那就是解决方案(我的想法被卡住了,正在寻找复杂的替代方案)。非常感谢。
    猜你喜欢
    • 2017-03-22
    • 2017-04-18
    • 2015-11-21
    • 1970-01-01
    • 2020-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多