Usare proxy in Swift con URLSession: guida pratica per iOS e macOS

Una guida code-first per configurare proxy residenziali in Swift tramite URLSession, con esempi su HTTP/SOCKS5, autenticazione, geo-targeting, async/await e best practice di produzione.

Using Proxies in Swift: A Code-First Guide to URLSession, SOCKS5 & Residential IPs
In questo articolo

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 = .reloadIgnoringLocalCacheData per evitare risposte stale.

Key Takeaways

Riepilogo dei punti chiave:

  • Usa connectionProxyDictionary con chiavi CFNetwork per configurare proxy HTTP/HTTPS/SOCKS5 in Swift senza librerie esterne.
  • Per l'autenticazione proxy, preferisci l'header Proxy-Authorization: Basic o 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 1080 con chiavi kCFStreamPropertySOCKSProxy*.
  • 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).

Domande frequenti

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.

Verifica la tua configurazione proxy in pochi secondi

Verificatore di proxy gratuito — conferma che i tuoi IP siano veloci, anonimi e non bloccati.

Controlla i proxy gratis
← Torna al Blog