Cos'è l'uso dei proxy in Swift e perché importa
Usare proxy in Swift significa instradare il traffico HTTP/HTTPS di URLSession attraverso un server intermediario, tipicamente per cambiare l'indirizzo IP di origine, accedere a contenuti regionalmente bloccati o evitare blocchi anti-bot su endpoint che rifiutano IP datacenter. Per gli sviluppatori iOS e macOS, la classe URLSessionConfiguration espone una proprietà chiamata connectionProxyDictionary che permette di definire proxy HTTP, HTTPS e SOCKS5 senza librerie esterne.
Il problema nasce quando endpoint pubblici — API di prezzo, SERP, contenuti geolocalizzati — applicano filtri basati sull'IP. Un IP datacenter (es. AWS, DigitalOcean) viene spesso bloccato o sottoposto a CAPTCHA, mentre un IP residenziale appare come un normale utente domestico. Secondo MDN, un proxy forward opera a livello di trasporto e applica le regole del client; in Swift questo si traduce nella configurazione del dizionario CFNetwork sottostante.
Contesto tecnico: perché URLSession ha bisogno di proxy residenziali
Le app iOS e macOS che consumano API pubbliche spesso incontrano limiti di rate o blocchi geo. Un proxy residenziale ruota IP tra dispositivi reali, riducendo la probabilità di ban. Rispetto a un datacenter proxy, un IP residenziale ha una reputazione più alta e una probabilità di blocco inferiore del 50% in scenari di scraping moderato. La latenza tipica di un gateway residenziale è di 200–500 ms, accettabile per la maggior parte dei casi d'uso non real-time.
Apple raccomanda di usare URLSession per tutto il networking moderno. Il documento ufficiale di Apple descrive URLSessionConfiguration come punto centralizzato per policy di connessione, inclusi proxy, timeout e ATS. La sfida è che le chiavi di autenticazione proxy native (kCFProxyUsernameKey, kCFProxyPasswordKey) sono inaffidabili su URLSession: la soluzione è gestire l'header Proxy-Authorization manualmente o implementare il delegate per la sfida 407.
Configurazione base: proxy HTTP/HTTPS con connectionProxyDictionary
Il modo più diretto per configurare un proxy in Swift è popolare connectionProxyDictionary con le chiavi CFNetwork. Per ProxyHat, il gateway HTTP è gate.proxyhat.com:8080.
import Foundation
func makeProxySession(username: String, password: String) -> URLSession {
let config = URLSessionConfiguration.default
config.connectionProxyDictionary = [
kCFNetworkProxiesHTTPEnable: true,
kCFNetworkProxiesHTTPProxy: "gate.proxyhat.com",
kCFNetworkProxiesHTTPPort: 8080,
kCFNetworkProxiesHTTPSEnable: true,
kCFNetworkProxiesHTTPSProxy: "gate.proxyhat.com",
kCFNetworkProxiesHTTPSPort: 8080
] as [String: Any]
config.timeoutIntervalForRequest = 30
config.timeoutIntervalForResource = 60
return URLSession(configuration: config)
}Questo instrada sia HTTP che HTTPS attraverso gate.proxyhat.com:8080. Nota: le chiavi kCFNetworkProxiesHTTPSEnable e kCFNetworkProxiesHTTPSProxy controllano il tunnel CONNECT per il traffico crittografato.
Autenticazione e geo-targeting: Proxy-Authorization e sfida 407
Le chiavi kCFProxyUsernameKey/kCFProxyPasswordKey non sono applicate in modo affidabile da URLSession. Due approcci funzionano: (1) aggiungere manualmente l'header Proxy-Authorization: Basic, o (2) implementare urlSession(_:didReceive:completionHandler:) per la sfida di autenticazione proxy (codice 407).
Con ProxyHat, il geo-targeting e le sessioni sticky si codificano nel campo username, ad esempio user-country-US-city-newyork-session-abc123.
import Foundation
func proxyAuthHeader(username: String, password: String) -> String {
let creds = "\(username):\(password)"
let encoded = Data(creds.utf8).base64EncodedString()
return "Basic \(encoded)"
}
func makeGeoProxySession(country: String, city: String, session: String, password: String) -> URLSession {
let username = "user-country-\(country)-city-\(city)-session-\(session)"
let config = URLSessionConfiguration.default
config.connectionProxyDictionary = [
kCFNetworkProxiesHTTPEnable: true,
kCFNetworkProxiesHTTPProxy: "gate.proxyhat.com",
kCFNetworkProxiesHTTPPort: 8080,
kCFNetworkProxiesHTTPSEnable: true,
kCFNetworkProxiesHTTPSProxy: "gate.proxyhat.com",
kCFNetworkProxiesHTTPSPort: 8080
] as [String: Any]
config.httpAdditionalHeaders = [
"Proxy-Authorization": proxyAuthHeader(username: username, password: password)
]
return URLSession(configuration: config)
}In alternativa, gestisci la sfida 407 con un delegate:
import Foundation
final class ProxyAuthDelegate: NSObject, URLSessionDelegate {
let username: String
let password: String
init(username: String, password: String) {
self.username = username
self.password = password
}
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPProxy ||
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPSProxy else {
completionHandler(.performDefaultHandling, nil)
return
}
let cred = URLCredential(user: username, password: password, persistence: .forSession)
completionHandler(.useCredential, cred)
}
}SOCKS5 su porta 1080 con chiavi kCFStreamPropertySOCKSProxy
Per il traffico che beneficia di SOCKS5 (es. tunnel TCP non-HTTP o compatibilità con certi client), ProxyHat espone la porta 1080. In Swift si usano le chiavi kCFStreamPropertySOCKSProxy*.
import Foundation
func makeSOCKS5Session(username: String, password: String) -> URLSession {
let config = URLSessionConfiguration.default
config.connectionProxyDictionary = [
kCFStreamPropertySOCKSProxy: true,
kCFStreamPropertySOCKSProxyHost: "gate.proxyhat.com",
kCFStreamPropertySOCKSProxyPort: 1080,
kCFStreamPropertySOCKSVersion: kCFStreamSocketVersionSOCKS5,
kCFStreamPropertySOCKSUser: username,
kCFStreamPropertySOCKSPassword: password
] as [String: Any]
return URLSession(configuration: config)
}L'autenticazione SOCKS5 username/password è supportata nativamente da CFNetwork, quindi qui non serve l'header Proxy-Authorization.
Esempio async/await con Codable e TaskGroup
Vediamo un esempio realistico di Swift web scraping con URLSession.shared.data(for:), decodifica Codable e concorrenza tramite TaskGroup. Usiamo una sessione con geo-targeting per raccogliere dati da più endpoint in parallelo.
import Foundation
struct PriceResult: Codable {
let sku: String
let price: Double
let currency: String
}
actor Scraper {
let session: URLSession
init(session: URLSession) { self.session = session }
func fetchPrice(for sku: String, region: String) async throws -> PriceResult {
var components = URLComponents(string: "https://api.example.com/prices/\(sku)")!
components.queryItems = [URLQueryItem(name: "region", value: region)]
var request = URLRequest(url: components.url!)
request.httpMethod = "GET"
request.timeoutInterval = 20
let (data, response) = try await session.data(for: request)
guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
throw URLError(.badServerResponse)
}
return try JSONDecoder().decode(PriceResult.self, from: data)
}
func fetchAll(skus: [String], region: String) async throws -> [PriceResult] {
try await withThrowingTaskGroup(of: PriceResult.self) { group in
for sku in skus {
group.addTask { try await self.fetchPrice(for: sku, region: region) }
}
var results: [PriceResult] = []
for try await result in group {
results.append(result)
}
return results
}
}
}
// Usage
let session = makeGeoProxySession(country: "US", city: "newyork", session: "abc123", password: "pass")
let scraper = Scraper(session: session)
Task {
do {
let results = try await scraper.fetchAll(skus: ["A1","B2","C3"], region: "US")
print(results)
} catch {
print("Scrape failed: \(error)")
}
}Questo approccio sfrutta la concorrenza strutturata di Swift, limitando implicitamente il parallelismo al numero di task nel gruppo. Per controllare la concorrenza massima, usa un semaforo o un pool di sessioni distinte.
Best practice di produzione: TLS, retry, ATS
Gestione TLS con URLSessionDelegate
Per endpoint con certificati self-signed o chain non standard, implementa urlSession(_:didReceive:completionHandler:) per la sfida TLS. In produzione, valida sempre la chain quando possibile.
import Foundation
final class TLSDelegate: NSObject, URLSessionDelegate {
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
let trust = challenge.protectionSpace.serverTrust {
completionHandler(.useCredential, URLCredential(trust: trust))
} else {
completionHandler(.performDefaultHandling, nil)
}
}
}
Retry con backoff esponenziale
import Foundation
func fetchWithRetry(session: URLSession, request: URLRequest, maxAttempts: Int = 3) async throws -> Data {
var attempt = 0
while true {
attempt += 1
do {
let (data, response) = try await session.data(for: request)
guard let http = response as? HTTPURLResponse else { return data }
if (200..<300).contains(http.statusCode) { return data }
if http.statusCode == 429 || http.statusCode >= 500 {
throw URLError(.timedOut)
}
throw URLError(.badServerResponse)
} catch {
if attempt >= maxAttempts { throw error }
let delay = pow(2.0, Double(attempt)) // 2s, 4s, 8s
try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
}
}
}App Transport Security (ATS)
Apple impone ATS di default, richiedendo TLS 1.2+ e forward secrecy. Per endpoint che non rispettano ATS, aggiungi eccezioni in Info.plist sotto NSAppTransportSecurity, ma preferisci sempre endpoint conformi. Consulta le linee guida ATS di Apple per i dettagli sulle chiavi.
Privacy on-device
Non memorizzare credenziali proxy in testo chiaro. Usa Keychain per conservare username/password e mai loggare header Proxy-Authorization. Su macOS, considera il sandbox e le entitlement di rete.
ProxyHat: configurazione e SDK
ProxyHat fornisce un gateway unificato su gate.proxyhat.com con porte 8080 (HTTP) e 1080 (SOCKS5). Il formato del username supporta geo-targeting a livello di paese e città, oltre a sessioni sticky per mantenere lo stesso IP tra richieste correlate.
Il ProxyHat SDK (disponibile per Python e Node.js) usa lo stesso gateway, quindi la logica di autenticazione e geo-targeting è identica a quella mostrata qui. Per le tariffe correnti consulta la pagina /it/pricing; per la lista di localizzazioni disponibili vedi /it/locations. Casi d'uso tipici sono descritti in /it/use-cases/web-scraping e /it/use-cases/serp-tracking.
Etica e considerazioni legali
Usare proxy per accedere a dati pubblici legittimi è generalmente accettabile, ma devi rispettare i termini di servizio delle piattaforme, il robots.txt e le normative sulla privacy. Negli Stati Uniti, il CFAA (Computer Fraud and Abuse Act) può applicarsi ad accessi non autorizzati; nell'UE, il GDPR disciplina il trattamento dei dati personali. Le linee guida dell'App Store vietano di usare API private o di eludere restrizioni di sistema.
Pratica raccomandata: preferisci sempre un'API ufficiale quando disponibile. Usa i proxy per dati pubblici non protetti da autenticazione e implementa rate limiting lato client per non sovraccaricare l'origine. Documenta la base legale del tuo scraping nel caso di uso commerciale.
Errori comuni e edge case
- Header Proxy-Authorization su HTTPS: l'header viene inviato solo al proxy durante il CONNECT, non all'origine. Se il gateway non lo riceve, usa il delegate 407.
- Sessioni sticky troppo lunghe: un'IP mantenuto per ore può essere flaggato. Ruota ogni 10–15 minuti per workload prolungati.
- Concorrenza eccessiva: più di 50 sessioni concorrenti da una singola app può saturare il gateway. Distribuisci su più sessioni URLSession.
- ATS troppo permissivo: disabilitare ATS globalmente espone a downgrade MITM. Usa eccezioni mirate.
- Cache di URLSession: per scraping, imposta
config.requestCachePolicy = .reloadIgnoringLocalCacheDataper evitare risposte stale.
Key Takeaways
Riepilogo dei punti chiave:
- Usa
connectionProxyDictionarycon chiavi CFNetwork per configurare proxy HTTP/HTTPS/SOCKS5 in Swift senza librerie esterne.- Per l'autenticazione proxy, preferisci l'header
Proxy-Authorization: Basico il delegate 407, poichékCFProxyUsernameKeyè inaffidabile su URLSession.- Il geo-targeting ProxyHat si codifica nel username:
user-country-US-city-newyork-session-abc123.- Per SOCKS5 usa la porta
1080con chiavikCFStreamPropertySOCKSProxy*.- Implementa retry con backoff esponenziale, valida TLS e usa Keychain per le credenziali.
- Rispetta ToS, robots.txt, CFAA, GDPR e le linee guida dell'App Store.
FAQ
Cos'è l'uso dei proxy in Swift?
Si tratta di instradare il traffico di URLSession attraverso un server intermediario configurando URLSessionConfiguration.connectionProxyDictionary con chiavi CFNetwork, per cambiare IP di origine, accedere a contenuti geolocalizzati o evitare blocchi anti-bot.
Perché l'uso dei proxy in Swift è importante per chi usa proxy?
Perché le app iOS/macOS che consumano API pubbliche spesso incontrano blocchi IP o limiti di rate. Un proxy residenziale fa apparire il traffico come proveniente da un utente domestico reale, riducendo ban e CAPTCHA rispetto agli IP datacenter.
Quale tipo di proxy funziona meglio per l'uso in Swift?
I proxy residenziali sono i più efficaci per scraping e accesso a contenuti regionalmente limitati, grazie alla loro reputazione elevata. I datacenter proxy sono più veloci ma più facilmente bloccati. SOCKS5 è utile per tunnel TCP non-HTTP.
Come evitare i blocchi implementando proxy in Swift?
Usa IP residenziali con rotazione, mantieni sessioni sticky di 10–15 minuti, implementa retry con backoff esponenziale, rispetta i limiti di rate lato client e configura header User-Agent realistici. Evita concorrenza eccessiva (sopra 50 sessioni simultanee).






