I am trying to convert a simple URLSession
request in Swift to using NWConnection
. This is because I want to make the request using a Proxy that requires Authentication. I posted this SO Question about using a proxy with URLSession
. Unfortunately no one answered it but I found a fix by using NWConnection
instead.
Working Request
func updateOrderStatus(completion: @escaping (Bool) -> Void) { let orderLink = "https://shop.ccs.com/51913883831/orders/f3ef2745f2b06c6b410e2aa8a6135847" guard let url = URL(string: orderLink) else { completion(true) return } let cookieStorage = HTTPCookieStorage.shared let config = URLSessionConfiguration.default config.httpCookieStorage = cookieStorage config.httpCookieAcceptPolicy = .always let session = URLSession(configuration: config) var request = URLRequest(url: url) request.httpMethod = "GET" request.setValue("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", forHTTPHeaderField: "Accept") request.setValue("none", forHTTPHeaderField: "Sec-Fetch-Site") request.setValue("navigate", forHTTPHeaderField: "Sec-Fetch-Mode") request.setValue("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", forHTTPHeaderField: "User-Agent") request.setValue("en-US,en;q=0.9", forHTTPHeaderField: "Accept-Language") request.setValue("gzip, deflate, br", forHTTPHeaderField: "Accept-Encoding") request.setValue("document", forHTTPHeaderField: "Sec-Fetch-Dest") request.setValue("u=0, i", forHTTPHeaderField: "Priority") // make the request }
Attempted Conversion
func updateOrderStatusProxy(completion: @escaping (Bool) -> Void) { let orderLink = "https://shop.ccs.com/51913883831/orders/f3ef2745f2b06c6b410e2aa8a6135847" guard let url = URL(string: orderLink) else { completion(true) return } let proxy = "resi.wealthproxies.com:8000:akzaidan:x0if46jo-country-US-session-7cz6bpzy-duration-60" let proxyDetails = proxy.split(separator: ":").map(String.init) guard proxyDetails.count == 4, let port = UInt16(proxyDetails[1]) else { print("Invalid proxy format") completion(false) 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 host = url.host ?? "" let path = url.path.isEmpty ? "/" : url.path let query = url.query ?? "" let fullPath = query.isEmpty ? path : "\(path)?\(query)" let connection = NWConnection( to: .hostPort( host: .init(host), port: .init(integerLiteral: UInt16(url.port ?? 80)) ), using: parameters ) connection.stateUpdateHandler = { state in switch state { case .ready: print("Connected to proxy: \(proxyDetails[0])") let httpRequest = """ GET \(fullPath) HTTP/1.1\r Host: \(host)\r Connection: close\r Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r 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 Accept-Language: en-US,en;q=0.9\r Accept-Encoding: gzip, deflate, br\r Sec-Fetch-Dest: document\r Sec-Fetch-Mode: navigate\r Sec-Fetch-Site: none\r Priority: u=0, i\r \r """ connection.send(content: httpRequest.data(using: .utf8), completion: .contentProcessed({ error in if let error = error { print("Failed to send request: \(error)") completion(false) return } // Read data until the connection is complete self.readAllData(connection: connection) { finalData, readError in if let readError = readError { print("Failed to receive response: \(readError)") completion(false) return } guard let data = finalData else { print("No data received or unable to read data.") completion(false) return } if let body = String(data: data, encoding: .utf8) { print("Received \(data.count) bytes") print("\n\nBody is \(body)") completion(true) } else { print("Unable to decode response body.") completion(false) } } })) case .failed(let error): print("Connection failed for proxy \(proxyDetails[0]): \(error)") completion(false) case .cancelled: print("Connection cancelled for proxy \(proxyDetails[0])") completion(false) case .waiting(let error): print("Connection waiting for proxy \(proxyDetails[0]): \(error)") completion(false) default: break } } connection.start(queue: .global()) } private func readAllData(connection: NWConnection, accumulatedData: Data = Data(), completion: @escaping (Data?, Error?) -> Void) { connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { data, context, isComplete, error in if let error = error { completion(nil, error) return } // Append newly received data to what's been accumulated so far let newAccumulatedData = accumulatedData + (data ?? Data()) if isComplete { // If isComplete is true, the server closed the connection or ended the stream completion(newAccumulatedData, nil) } else { // Still more data to read, so keep calling receive self.readAllData(connection: connection, accumulatedData: newAccumulatedData, completion: completion) } } }