Как делать синхронные URL-запросы с помощью Swift 3

Я знаю, что этот вопрос задавался раньше, и я согласен с большинством ответов, в которых утверждается, что лучше следить за тем, как запросы выполняются асинхронно с URLSession в Swift 3. У меня есть следующий сценарий, где асинхронный запрос не может использоваться.

Со Swift 3 и возможностью быстрой работы на серверах у меня возникает следующая проблема.

  1. Сервер получает запрос от клиента
  2. Чтобы обработать запрос, сервер должен отправить запрос URL и дождаться получения ответа.
  3. После получения ответа обработайте его и ответьте клиенту.

Проблема возникает на шаге 2, где URLSession дает нам возможность инициировать только задачу асинхронных данных. Большинство (если не все) серверных быстрых веб-фреймворков не поддерживают асинхронные ответы. Когда запрос поступает на сервер, все должно выполняться синхронно и в конце отправлять ответ.

Единственное решение, которое я нашел до сих пор, - это использование 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, и именно здесь я столкнулся с проблемой. Я полагаю, что подобные проблемы существуют и во всех других быстрых веб-фреймворках.


person zirinisp    schedule 24.10.2016    source источник


Ответы (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

person Youming Lin    schedule 24.10.2016
comment
Никогда не думал об этом простом решении. У меня создалось впечатление, что вы должны использовать res в том же потоке, а не в блоке завершения. Когда вы говорите, что это не лучшая практика, не могли бы вы объяснить, почему? - person zirinisp; 25.10.2016
comment
@zirinisp Я не включил ни надлежащую обработку ошибок, ни необязательную распаковку (if let data = data). Вы определенно должны включить обработку ошибок в свой производственный код как для ошибки из dataTask, так и для возможной ошибки из res.send (возможно, повторно выбросить вторую). - person Youming Lin; 25.10.2016
comment
Я подумал, что отправка ответа из блока - не лучшая практика. Если это не проблема, то это решение (мой разум застрял и искал сложные альтернативы). Большое тебе спасибо. - person zirinisp; 25.10.2016

В 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)
    }
}
person tanner0101    schedule 24.10.2016
comment
Похоже, Vapor все это пережила. Мне больше нравится второй подход, поскольку он универсален для любого сценария (не привязан к конкретной структуре) - person zirinisp; 25.10.2016