3

I know the question has been asked before and I agree with most answers that claim it is better to follow the way requests are made async with URLSession in Swift 3. I haver the following scenario, where async request cannot be used.

With Swift 3 and the ability to run swift on servers I have the following problem.

  1. Server Receives a request from a client
  2. To process the request the server has to send a url request and wait for the response to arrive.
  3. Once response arrives, process it and reply to the client

The problem arrises in step 2, where URLSession gives us the ability to initiate an async data task only. Most (if not all) server side swift web frameworks do not support async responses. When a request arrives to the server everything has to be done in a synchronous matter and at the end send the response.

The only solution I have found so far is using DispatchSemaphore (see example at the end) and I am not sure whether that will work in a scaled environment.

Any help or thoughts would be appreciated.

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)

        }
    }
}

I only have experience with kitura web framework and this is where i faced the problem. I suppose that similar problems exist in all other swift web frameworks.

zirinisp
  • 9,971
  • 5
  • 32
  • 38

2 Answers2

2

In Vapor, you can use the Droplet's client to make synchronous requests.

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

Additionally, you can use the Portal class to make asynchronous tasks synchronous.

let res = try Portal.open { portal in
    asyncClient.get("https://httpbin.org") { res in
        portal.close(with: res)
    }
}
tanner0101
  • 4,005
  • 19
  • 33
  • Looks like Vapor has though this through. I like the second approach better as it is generic to any scenario (not bind to a specific framework) – zirinisp Oct 25 '16 at 13:26
1

Your three-step problem can be solved via the use of a completion handler, i.e., a callback handler a la Node.js convention:

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()

This is a quick demo of a solution to your problem, and it is by no means following best Swift/Kitura practices. But, with the use of a completion handler, I am able to have my Kitura app make an HTTP call to fetch the resource at http://www.example.com, wait for the response, and then send the result back to my app's client.

Link to the relevant API: https://developer.apple.com/reference/foundation/urlsession/1410330-datatask

Youming Lin
  • 334
  • 2
  • 4
  • Never thought of that simple solution. I was under the impression that you are suppose to use "res" in the same thread and not in a completion block. When you say "not best practices" can you please explain why? – zirinisp Oct 25 '16 at 13:22
  • @zirinisp I didn't include proper error handling nor optional unwrapping (`if let data = data`). You most definitely should include error handling in your production code for both the error from `dataTask` and the possible error from `res.send` (maybe rethrow the second one). – Youming Lin Oct 25 '16 at 14:21
  • I though that the "not best practise" goes to sending a response from a block. As long as that is not a problem, then that is the solution (my mind got stuck and was looking at complicated alternatives). Thank you very much. – zirinisp Oct 25 '16 at 14:27