Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 769
zirinisp Avatar asked Dec 20 '25 14:12

zirinisp


2 Answers

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)
    }
}
like image 114
tanner0101 Avatar answered Dec 23 '25 08:12

tanner0101


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

like image 24
Youming Lin Avatar answered Dec 23 '25 09:12

Youming Lin