Swift中URLSession转NWConnection代理请求无响应问题求助
解决NWConnection通过认证代理发起HTTPS请求无响应的问题
针对你将URLSession GET请求转换为NWConnection时遇到的无响应问题,以下是核心问题排查和修正方案:
核心问题分析
- HTTPS端口错误:目标URL为HTTPS协议,默认端口应为443,但原代码使用
url.port ?? 80,导致连接到非HTTPS端口,无法建立正确加密连接。 - 缺少TLS配置:原代码使用
NWParameters.tcp未启用TLS加密,无法与HTTPS服务器通信。 - HTTP请求换行符不规范:原请求使用
\r作为换行,不符合HTTP标准的\r\n,可能导致代理或服务器无法正确解析请求。 - 代理配置关联逻辑问题:原代码中PrivacyContext的设置方式未确保代理配置生效,需调整参数关联逻辑。
修正后的完整代码
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: NWEndpoint.Host(proxyDetails[0]), port: NWEndpoint.Port(integerLiteral: port)) let proxyConfig = NWParameters.ProxyConfiguration(httpCONNECTProxy: proxyEndpoint, tlsOptions: nil) proxyConfig.applyCredential(username: proxyDetails[2], password: proxyDetails[3]) // 配置TLS参数,适配HTTPS请求 let tlsOptions = NWProtocolTLS.Options() sec_protocol_options_set_verify_block(tlsOptions.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue() var error: CFError? if SecTrustEvaluateWithError(trust, &error) { sec_protocol_verify_complete(true) } else { sec_protocol_verify_complete(false) } }, DispatchQueue.global()) let parameters = NWParameters.tls(options: tlsOptions) // 直接关联代理配置到参数 parameters.proxyConfigurations = [proxyConfig] let host = url.host ?? "" let path = url.path.isEmpty ? "/" : url.path let query = url.query ?? "" let fullPath = query.isEmpty ? path : "\(path)?\(query)" // 使用HTTPS默认端口443 let connection = NWConnection( to: .hostPort( host: NWEndpoint.Host(host), port: NWEndpoint.Port(integerLiteral: UInt16(url.port ?? 443)) ), using: parameters ) connection.stateUpdateHandler = { state in switch state { case .ready: print("Connected to proxy: \(proxyDetails[0])") // 使用标准HTTP换行符\r\n let httpRequest = """ GET \(fullPath) HTTP/1.1\r\n Host: \(host)\r\n Connection: close\r\n Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\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 Accept-Language: en-US,en;q=0.9\r\n Accept-Encoding: gzip, deflate, br\r\n Sec-Fetch-Dest: document\r\n Sec-Fetch-Mode: navigate\r\n Sec-Fetch-Site: none\r\n Priority: u=0, i\r\n \r\n """ guard let requestData = httpRequest.data(using: .utf8) else { print("Failed to convert request to data") completion(false) return } connection.send(content: requestData, completion: .contentProcessed({ error in if let error = error { print("Failed to send request: \(error)") completion(false) return } // 读取响应数据 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 responseString = self.decompressResponse(data: data) { print("Received \(data.count) bytes") print("\n\nBody is \(responseString)") completion(true) } else if let body = String(data: data, encoding: .utf8) { print("Received \(data.count) bytes (uncompressed)") 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 } let newAccumulatedData = accumulatedData + (data ?? Data()) if isComplete { completion(newAccumulatedData, nil) } else { self.readAllData(connection: connection, accumulatedData: newAccumulatedData, completion: completion) } } } // 处理gzip/deflate/br压缩的响应 private func decompressResponse(data: Data) -> String? { guard let responseString = String(data: data, encoding: .utf8), let headerEndIndex = responseString.range(of: "\r\n\r\n")?.upperBound else { return nil } let headersString = String(responseString[..<headerEndIndex]) let bodyData = data.subdata(in: headerEndIndex.utf16Offset(in: responseString)..<data.count) if headersString.contains("Content-Encoding: gzip") { do { let decompressedData = try Data(contentsOf: GzipArchive(data: bodyData)) return String(data: decompressedData, encoding: .utf8) } catch { print("Failed to decompress gzip data: \(error)") return nil } } else if headersString.contains("Content-Encoding: deflate") { do { let decompressedData = try Data(contentsOf: DeflateArchive(data: bodyData)) return String(data: decompressedData, encoding: .utf8) } catch { print("Failed to decompress deflate data: \(error)") return nil } } else if headersString.contains("Content-Encoding: br") { do { let decompressedData = try BrotliDecoder.decode(bodyData) return String(data: decompressedData, encoding: .utf8) } catch { print("Failed to decompress br data: \(error)") return nil } } return nil }
关键修改说明
- HTTPS端口修正:将目标端口改为443,适配HTTPS协议要求。
- 添加TLS配置:创建支持加密的NWParameters,确保与HTTPS服务器的安全通信。
- 规范HTTP请求格式:统一使用
\r\n作为换行符,符合HTTP协议标准。 - 调整代理配置方式:直接将代理配置关联到NWParameters,确保代理生效。
- 添加压缩响应处理:针对请求头中的
Accept-Encoding,新增gzip/deflate/br解压逻辑,正确解析服务器返回的压缩内容。
内容的提问来源于stack exchange,提问作者Ahmed Zaidan
相关产品推荐
相关产品推荐

