Search code examples
swift3alamofirevaporkitura

How to make synchronous url requests with swift 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.


Solution

  • 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