Use proxy for http request from iOS device

I'm simply trying to use a proxy to route a http request in Swift to measure the average round trip time of a list of proxies. I've went through multiple Stack Overflow threads on this topic but they are all super old / outdated.

format:host:port:username:password

I also added the info.plist entry:

NSAllowsArbitraryLoads -> NSExceptionDomains

When I call the function below I am prompted with a menu that says

"Proxy authentication required. Enter the password for HTTP proxy ... in settings"

I closed this menu inside my app and tried the function below again and it worked without giving me the menu a second time. However even though the function works without throwing any errors, it does NOT use the proxies to route the request.

Why does the request work (throws no errors) but does not use the proxies? I'm assuming it's because the password isn't entered in the settings as the alert said. My users will want to test proxy speeds for many different Hosts/Ports, it doesn't make sense to enter the password in settings every time. How can I fix this issue?

func averageProxyGroupSpeed(proxies: [String], completion: @escaping (Int, String) -> Void) {
    let numProxies = proxies.count
    if numProxies == 0 {
        completion(0, "No proxies")
        return
    }

    var totalTime: Int64 = 0
    var successCount = 0
    let group = DispatchGroup()
    let queue = DispatchQueue(label: "proxyQueue", attributes: .concurrent)
    let lock = NSLock()

    let shuffledProxies = proxies.shuffled()
    let selectedProxies = Array(shuffledProxies.prefix(25))

    for proxy in selectedProxies {
        group.enter()
        queue.async {
            let proxyDetails = proxy.split(separator: ":").map(String.init)
            guard proxyDetails.count == 4,
                  let port = Int(proxyDetails[1]),
                  let url = URL(string: "http://httpbin.org/get") else {
                completion(0, "Invalid proxy format")
                group.leave()
                return
            }

            var request = URLRequest(url: url)
            request.timeoutInterval = 15

            let configuration = URLSessionConfiguration.default
            configuration.connectionProxyDictionary = [
                AnyHashable("HTTPEnable"): true,
                AnyHashable("HTTPProxy"): proxyDetails[0],
                AnyHashable("HTTPPort"): port,
                AnyHashable("HTTPSEnable"): false,
                AnyHashable("HTTPUser"): proxyDetails[2],
                AnyHashable("HTTPPassword"): proxyDetails[3]
            ]

            let session = URLSession(configuration: configuration)
            let start = Date()

            let task = session.dataTask(with: request) { _, _, error in
                defer { group.leave() }
                
                if let error = error {
                    print("Error: \(error.localizedDescription)")
                } else {
                    let duration = Date().timeIntervalSince(start) * 1000

                    lock.lock()
                    totalTime += Int64(duration)
                    successCount += 1
                    lock.unlock()
                }
            }
            task.resume()
        }
    }

    group.notify(queue: DispatchQueue.main) {
        if successCount == 0 {
            completion(0, "Proxies Failed")
        } else {
            let averageTime = Int(Double(totalTime) / Double(successCount))
            completion(averageTime, "")
        }
    }
}
Answered by DTS Engineer in 824936022

I have a bunch of feedback here.

I recommend that you use the new proxyConfigurations property rather than the legacy connectionProxyDictionary. It has more features and it’s easier to avoid mistakes.

Regarding authentication, I doubt you’ll be able to make this work with authenticating proxies. URLSession has a long history of not dealing with authenticating against a custom proxy correctly. You might be able to get this working by handling the authentication challenges via the session delegate. You can tell a challenge is for a proxy by looking at the isProxy property on the protection space. However, I vaguely recall that this is broken too. Still, it’s worth trying.

Finally, your mechanism for measuring perform seems… well… kinda naïve. I recommend that you instead adopt task metrics. Also, if you want meaningful numbers, you need to run a bunch of requests. Remember that there’s a lot of latency associated with connection setup, and that increases when you involve proxies.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

I have a bunch of feedback here.

I recommend that you use the new proxyConfigurations property rather than the legacy connectionProxyDictionary. It has more features and it’s easier to avoid mistakes.

Regarding authentication, I doubt you’ll be able to make this work with authenticating proxies. URLSession has a long history of not dealing with authenticating against a custom proxy correctly. You might be able to get this working by handling the authentication challenges via the session delegate. You can tell a challenge is for a proxy by looking at the isProxy property on the protection space. However, I vaguely recall that this is broken too. Still, it’s worth trying.

Finally, your mechanism for measuring perform seems… well… kinda naïve. I recommend that you instead adopt task metrics. Also, if you want meaningful numbers, you need to run a bunch of requests. Remember that there’s a lot of latency associated with connection setup, and that increases when you involve proxies.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Sorry I didn't respond here. I missed your comment, something I explain in Quinn’s Top Ten DevForums Tips. It’s always better to reply as a reply.

Anyway, let's pick up this conversation in your other thread.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Use proxy for http request from iOS device
 
 
Q