swifthttpnetwork-programminghttpsrequest

Bad HTTP request when using Swift


I am trying to use a proxy with auth in Swift. I finally got it working using NWConnection. And I can see from my proxy server that it's actually getting the request from my iOS app when I call this function. However I get the error response below. I am assuming something about my request to httpbin.org is wrong but I have not been able to figure it out. The proxy in the array is working and feel free to use it if you want to test.

Connected to proxy: resi.wealthproxies.com
Response from proxy resi.wealthproxies.com: HTTP/1.1 400 Bad Request

Server: awselb/2.0

Date: Thu, 13 Feb 2025 21:31:28 GMT

Content-Type: text/html

Content-Length: 122

Connection: close

<html>

<head><title>400 Bad Request</title></head>

<body>

<center><h1>400 Bad Request</h1></center>

</body>

</html>
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 = ["resi.wealthproxies.com:8000:akzaidan:x0if46jo-country-US-session-niwtz2y7-duration-60"]

    for proxy in selectedProxies {
        group.enter()
        queue.async {
            let proxyDetails = proxy.split(separator: ":").map(String.init)
            guard proxyDetails.count == 4,
                  let port = UInt16(proxyDetails[1]) else {
                completion(0, "Invalid proxy format")
                group.leave()
                return
            }

            let proxyEndpoint = NWEndpoint.hostPort(host: .init(proxyDetails[0]), port: NWEndpoint.Port(integerLiteral: port))
            let proxyConfig = ProxyConfiguration(httpCONNECTProxy: proxyEndpoint, tlsOptions: nil)
            proxyConfig.applyCredential(username: proxyDetails[2], password: proxyDetails[3])

            let parameters = NWParameters.tcp
            let privacyContext = NWParameters.PrivacyContext(description: "ProxyConfig")
            privacyContext.proxyConfigurations = [proxyConfig]
            parameters.setPrivacyContext(privacyContext)

            let connection = NWConnection(to: .hostPort(host: "httpbin.org", port: 80), using: parameters)

            let start = Date()
            
            connection.stateUpdateHandler = { state in
                switch state {
                case .ready:
                    print("Connected to proxy: \(proxyDetails[0])")
                                        
                    let httpRequest = """
                    GET http://httpbin.org/get HTTP/1.1\r\n
                    Host: httpbin.org\r\n
                    Connection: close\r\n
                    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n
                    Accept-Encoding: gzip, deflate\r\n
                    Accept-Language: en-US,en;q=0.9\r\n
                    Upgrade-Insecure-Requests: 1\r\n
                    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0.1 Safari/605.1.15\r\n
                    \r\n
                    """
                    
                    connection.send(content: httpRequest.data(using: .utf8), completion: .contentProcessed({ error in
                        if let error = error {
                            print("Failed to send request: \(error)")
                            group.leave()
                        } else {
                            // Receive the response
                            connection.receive(minimumIncompleteLength: 1, maximumLength: 4096) { data, _, isComplete, error in
                                defer { group.leave() }
                                
                                if let error = error {
                                    print("Failed to receive response: \(error)")
                                } else if isComplete {
                                    print("complete")
                                    
                                    if let data {
                                        let responseString = String(data: data, encoding: .utf8) ?? ""
                                        print("Response from proxy \(proxyDetails[0]): \(responseString)")
                                    } else {
                                        print("data len is \(data?.count ?? -6)")
                                    }
                                    
                                    let duration = Date().timeIntervalSince(start) * 1000 // Convert to milliseconds
                                    
                                    lock.lock()
                                    totalTime += Int64(duration)
                                    successCount += 1
                                    lock.unlock()
                                } else if let data {
                                    let responseString = String(data: data, encoding: .utf8) ?? ""
                                    print("Response from proxy \(proxyDetails[0]): \(responseString)")
                                } else {
                                    print("Not complete")
                                }
                            }
                        }
                    }))
                case .failed(let error):
                    print("Connection failed for proxy \(proxyDetails[0]): \(error)")
                    group.leave()
                case .cancelled:
                    print("Connection cancelled for proxy \(proxyDetails[0])")
                    group.leave()
                case .waiting(let error):
                    print("Connection waiting for proxy \(proxyDetails[0]): \(error)")
                    group.leave()
                default:
                    break
                }
            }

            connection.start(queue: queue)
        }
    }

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

Solution

  • The issue was the formatting of the HTTP request within the string. The correct format is:

    let httpRequest = "GET /get HTTP/1.1\r\nHost: httpbin.org\r\nConnection: close\r\nAccept: */*\r\nUser-Agent: MySwiftApp/1.0\r\n\r\n"

    This code enables requests in Swift to utilize a Proxy with Auth. The only way I found to Authenticate a proxy in swift was with NWConnection. All the other native methods didn't work.

    import Foundation
    import Network
    
    func averageProxyGroupSpeed() {
        var successCount = 0
        let group = DispatchGroup()
        let queue = DispatchQueue(label: "proxyQueue", attributes: .concurrent)
        let lock = NSLock()
    
        let selectedProxies = ["107.180.132.32:3128:slgayamo:xxeeikxt"]
    
        for proxy in selectedProxies {
            group.enter()
            queue.async {
                let proxyDetails = proxy.split(separator: ":").map(String.init)
                guard proxyDetails.count == 4,
                      let port = UInt16(proxyDetails[1]) else {
                    group.leave()
                    return
                }
    
                let proxyEndpoint = NWEndpoint.hostPort(host: .init(proxyDetails[0]), port: NWEndpoint.Port(integerLiteral: port))
                let proxyConfig = ProxyConfiguration(httpCONNECTProxy: proxyEndpoint, tlsOptions: nil)
                proxyConfig.applyCredential(username: proxyDetails[2], password: proxyDetails[3])
    
                let parameters = NWParameters.tcp
                let privacyContext = NWParameters.PrivacyContext(description: "ProxyConfig")
                privacyContext.proxyConfigurations = [proxyConfig]
                parameters.setPrivacyContext(privacyContext)
    
                let connection = NWConnection(to: .hostPort(host: "httpbin.org", port: 80), using: parameters)
                
                let start = Date()
                
                connection.stateUpdateHandler = { state in
                    switch state {
                    case .ready:
                        print("Connected to proxy: \(proxyDetails[0])")
                                            
                        let httpRequest = "GET /get HTTP/1.1\r\nHost: httpbin.org\r\nConnection: close\r\nAccept: */*\r\nUser-Agent: MySwiftApp/1.0\r\n\r\n"
                                            
                        
                        connection.send(content: httpRequest.data(using: .utf8), completion: .contentProcessed({ error in
                            if let error = error {
                                print("Failed to send request: \(error)")
                                group.leave()
                            } else {
                                connection.receive(minimumIncompleteLength: 1, maximumLength: 4096) { data, _, isComplete, error in
                                    defer { group.leave() }
                                    
                                    if let error = error {
                                        print("Failed to receive response: \(error)")
                                    } else if isComplete {
                                        print("complete")
                                        
                                        if let data {
                                            let responseString = String(data: data, encoding: .utf8) ?? ""
                                            print("Response from proxy \(proxyDetails[0]): \(responseString)")
                                        } else {
                                            print("data len is \(data?.count ?? -6)")
                                        }
                                        
                                        let duration = Date().timeIntervalSince(start) * 1000 // Convert to milliseconds
                                        
                                    } else if let data {
                                        let responseString = String(data: data, encoding: .utf8) ?? ""
                                        print("Response from proxy \(proxyDetails[0]): \(responseString)")
                                    } else {
                                        print("Not complete")
                                    }
                                }
                            }
                        }))
                    case .failed(let error):
                        print("Connection failed for proxy \(proxyDetails[0]): \(error)")
                        group.leave()
                    case .cancelled:
                        print("Connection cancelled for proxy \(proxyDetails[0])")
                        group.leave()
                    case .waiting(let error):
                        print("Connection waiting for proxy \(proxyDetails[0]): \(error)")
                        group.leave()
                    default:
                        break
                    }
                }
    
                connection.start(queue: queue)
            }
        }
    
        group.notify(queue: DispatchQueue.main) {
            // Handle completion if needed
        }
    }
    
    averageProxyGroupSpeed()