Configurare proxy HTTP in Java può sembrare semplice finché non devi gestire autenticazione, rotazione IP, timeout e scraping parallelo su larga scala. Questa guida mostra implementazioni production-ready con Java 11+ HttpClient, OkHttp, Jsoup e Apache HttpClient, con pattern completi per pooling, retry e gestione TLS.
Perché i Proxy sono Essenziali per Applicazioni Java
Quando costruisci scraper, monitor di prezzi o sistemi di raccolta dati, un singolo IP viene rapidamente bloccato. I proxy residenziali distribuiscono le richieste su migliaia di IP reali, rendendo il traffico indistinguibile da utenti legittimi.
ProxyHat offre proxy residenziali, mobile e datacenter accessibili via un singolo gateway:
- Gateway:
gate.proxyhat.com - Porta HTTP:
8080 - Porta SOCKS5:
1080 - Autenticazione: username/password con flag per geo-targeting e sessioni sticky
Java 11+ HttpClient con ProxySelector e Authenticator
Java 11 ha introdotto il moderno java.net.http.HttpClient, un client HTTP reattivo con supporto nativo per HTTP/2. La configurazione proxy avviene tramite ProxySelector e l'autenticazione tramite Authenticator.
Configurazione Base con ProxySelector
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
public class JavaHttpClientProxyExample {
private static final String PROXY_HOST = "gate.proxyhat.com";
private static final int PROXY_PORT = 8080;
private static final String PROXY_USER = "your-username";
private static final String PROXY_PASS = "your-password";
public static void main(String[] args) throws Exception {
// Crea un ProxySelector personalizzato che instrada tutto via proxy
ProxySelector proxySelector = new ProxySelector() {
@Override
public List<Proxy> select(URI uri) {
// Instrada tutte le richieste HTTP/HTTPS attraverso il proxy
return List.of(new Proxy(Proxy.Type.HTTP,
new InetSocketAddress(PROXY_HOST, PROXY_PORT)));
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
System.err.println("Connessione proxy fallita: " + ioe.getMessage());
}
};
// Configura l'Authenticator per le credenziali proxy
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
// Verifica che la richiesta provenga dal proxy
if (getRequestorType() == RequestorType.PROXY) {
return new PasswordAuthentication(
PROXY_USER,
PROXY_PASS.toCharArray()
);
}
return null;
}
};
// Costruisci il client con proxy, auth, timeout e conn pooling
HttpClient client = HttpClient.newBuilder()
.proxy(proxySelector)
.authenticator(authenticator)
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.version(HttpClient.Version.HTTP_2)
.build();
// Esegui una richiesta GET
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/ip"))
.timeout(Duration.ofSeconds(30))
.GET()
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("Status: " + response.statusCode());
System.out.println("Body: " + response.body());
}
}
Rotazione IP con Username Dinamico
ProxyHat permette di controllare la rotazione IP direttamente nell'username. Aggiungi -country-XX per geo-targeting o -session-XXX per sessioni sticky.
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class ProxyHatRotatingExample {
private static final String PROXY_HOST = "gate.proxyhat.com";
private static final int PROXY_PORT = 8080;
private static final String BASE_USER = "your-username";
private static final String PROXY_PASS = "your-password";
// Genera un ID sessione univoco per IP sticky
private static String generateSessionId() {
return "sess-" + Long.toHexString(ThreadLocalRandom.current().nextLong());
}
// Costruisce username con geo-targeting USA e sessione sticky
private static String buildUsername(String sessionId) {
return BASE_USER + "-country-US-session-" + sessionId;
}
public static void main(String[] args) throws Exception {
String sessionId = generateSessionId();
String username = buildUsername(sessionId);
System.out.println("Session ID: " + sessionId);
System.out.println("Username: " + username);
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
if (getRequestorType() == RequestorType.PROXY) {
return new PasswordAuthentication(username, PROXY_PASS.toCharArray());
}
return null;
}
};
ProxySelector proxySelector = new ProxySelector() {
@Override
public List<Proxy> select(URI uri) {
return List.of(new Proxy(Proxy.Type.HTTP,
new InetSocketAddress(PROXY_HOST, PROXY_PORT)));
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
System.err.println("Proxy connection failed: " + ioe.getMessage());
}
};
HttpClient client = HttpClient.newBuilder()
.proxy(proxySelector)
.authenticator(authenticator)
.connectTimeout(Duration.ofSeconds(15))
.build();
// Verifica l'IP in uscita
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/ip"))
.GET()
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString()
);
System.out.println("Response: " + response.body());
}
}
OkHttp con Proxy e Authenticator
OkHttp di Square è il client HTTP più usato su Android e in molti progetti JVM. Offre un sistema flessibile di intercettori, pooling di connessioni e supporto WebSocket nativo.
Configurazione Base con Autenticazione
import okhttp3.Authenticator;
import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
public class OkHttpProxyExample {
private static final String PROXY_HOST = "gate.proxyhat.com";
private static final int PROXY_PORT = 8080;
private static final String PROXY_USER = "your-username";
private static final String PROXY_PASS = "your-password";
public static void main(String[] args) throws Exception {
// Configura il proxy HTTP
Proxy proxy = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress(PROXY_HOST, PROXY_PORT)
);
// Authenticator per gestire il challenge 407 Proxy Authentication Required
Authenticator proxyAuthenticator = new Authenticator() {
@Override
public Request authenticate(Route route, Response response) {
// Evita loop infiniti se l'autenticazione fallisce ripetutamente
if (response.responseCount() >= 3) {
return null; // Rinuncia
}
String credential = Credentials.basic(PROXY_USER, PROXY_PASS);
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
}
};
// Costruisci il client con timeout e connection pool configurati
OkHttpClient client = new OkHttpClient.Builder()
.proxy(proxy)
.proxyAuthenticator(proxyAuthenticator)
.connectTimeout(Duration.ofSeconds(15))
.readTimeout(Duration.ofSeconds(30))
.writeTimeout(Duration.ofSeconds(30))
// Connection pool: 10 connessioni keepalive per 5 minuti
.connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES))
// Retry automatico su fallimenti di connessione
.retryOnConnectionFailure(true)
.build();
Request request = new Request.Builder()
.url("https://httpbin.org/headers")
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println("Status: " + response.code());
System.out.println("Body: " + response.body().string());
}
}
}
Interceptore per Retry con Backoff Esponenziale
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import java.io.IOException;
import java.time.Duration;
import java.util.concurrent.ThreadLocalRandom;
public class RetryInterceptor implements Interceptor {
private final int maxRetries;
private final long initialBackoffMs;
private final double backoffMultiplier;
public RetryInterceptor(int maxRetries, long initialBackoffMs, double backoffMultiplier) {
this.maxRetries = maxRetries;
this.initialBackoffMs = initialBackoffMs;
this.backoffMultiplier = backoffMultiplier;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
IOException lastException = null;
for (int attempt = 0; attempt <= maxRetries; attempt++) {
try {
response = chain.proceed(request);
// Retry su 429 (rate limit) o 5xx server errors
if (response.isSuccessful() || response.code() < 500 && response.code() != 429) {
return response;
}
// Chiudi la risposta prima del retry
ResponseBody body = response.body();
if (body != null) body.close();
} catch (IOException e) {
lastException = e;
}
// Calcola backoff con jitter
if (attempt < maxRetries) {
long backoff = (long) (initialBackoffMs * Math.pow(backoffMultiplier, attempt));
long jitter = ThreadLocalRandom.current().nextLong((long)(backoff * 0.3));
long sleepTime = backoff + jitter;
System.out.printf("Retry %d/%d, sleeping %dms%n", attempt + 1, maxRetries, sleepTime);
try {
Thread.sleep(sleepTime);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new IOException("Retry interrupted", ie);
}
}
}
if (lastException != null) {
throw lastException;
}
return response;
}
}
// Utilizzo:
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new RetryInterceptor(3, 1000, 2.0))
.build();
Jsoup con Supporto Proxy per Parsing HTML
Jsoup è la libreria di riferimento per parsing HTML in Java. Estrae dati da pagine web con selettori CSS simili a jQuery. Supporta proxy nativamente tramite Connection.proxy().
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.Map;
public class JsoupProxyExample {
private static final String PROXY_HOST = "gate.proxyhat.com";
private static final int PROXY_PORT = 8080;
private static final String PROXY_USER = "your-username";
private static final String PROXY_PASS = "your-password";
public static void main(String[] args) throws Exception {
// Configura proxy HTTP
Proxy proxy = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress(PROXY_HOST, PROXY_PORT)
);
// Jsoup connessione con proxy e autenticazione
Document doc = Jsoup.connect("https://news.ycombinator.com/")
.proxy(proxy)
.header("Proxy-Authorization",
java.util.Base64.getEncoder().encodeToString(
(PROXY_USER + ":" + PROXY_PASS).getBytes()
))
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.timeout(30000)
.followRedirects(true)
.ignoreContentType(true)
.ignoreHttpErrors(true)
.maxBodySize(10 * 1024 * 1024) // 10 MB max
.get();
// Estrai titoli e link delle storie
Elements stories = doc.select("tr.athing");
System.out.println("Trovate " + stories.size() + " storie:\n");
for (Element story : stories) {
String title = story.select("span.titleline > a").text();
String link = story.select("span.titleline > a").attr("href");
String score = story.nextElementSibling()
.select("span.score").text();
System.out.printf("- %s%n Score: %s%n Link: %s%n%n",
title, score, link);
}
}
}
Wrapping Jsoup con OkHttp per Maggiore Controllo
Per scenari avanzati, usa OkHttp per la richiesta e Jsoup solo per il parsing. Questo ti dà accesso a interceptori, connection pool e retry policy.
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.net.InetSocketAddress;
import java.net.Proxy;
public class JsoupOkHttpHybrid {
public static void main(String[] args) throws Exception {
Proxy proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress("gate.proxyhat.com", 8080));
OkHttpClient client = new OkHttpClient.Builder()
.proxy(proxy)
.proxyAuthenticator((route, response) -> {
String cred = okhttp3.Credentials.basic("user", "pass");
return response.request().newBuilder()
.header("Proxy-Authorization", cred)
.build();
})
.build();
Request request = new Request.Builder()
.url("https://example.com/products")
.header("User-Agent", "Mozilla/5.0")
.build();
try (Response response = client.newCall(request).execute()) {
String html = response.body().string();
// Parsing con Jsoup
Document doc = Jsoup.parse(html, response.request().url().toString());
doc.select("div.product").forEach(product -> {
String name = product.select("h3").text();
String price = product.select(".price").text();
System.out.println(name + " - " + price);
});
}
}
}
Apache HttpClient (Compatibilità Legacy)
Molti sistemi enterprise usano ancora Apache HttpClient 4.x. La configurazione proxy richiede HttpHost e CredentialsProvider.
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
public class ApacheHttpClientProxyExample {
public static void main(String[] args) throws Exception {
// Configura il proxy host
HttpHost proxy = new HttpHost("gate.proxyhat.com", 8080);
// Credenziali per autenticazione proxy
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope(proxy),
new UsernamePasswordCredentials("your-username", "your-password")
);
// Connection pool manager
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(100); // Max connessioni totali
cm.setDefaultMaxPerRoute(20); // Max per route
// Request config con timeout e proxy
RequestConfig requestConfig = RequestConfig.custom()
.setProxy(proxy)
.setConnectTimeout(15000)
.setSocketTimeout(30000)
.setConnectionRequestTimeout(10000)
.build();
try (CloseableHttpClient client = HttpClients.custom()
.setConnectionManager(cm)
.setDefaultCredentialsProvider(credsProvider)
.setDefaultRequestConfig(requestConfig)
.build()) {
HttpGet get = new HttpGet("https://httpbin.org/ip");
org.apache.http.HttpResponse response = client.execute(get);
String body = EntityUtils.toString(response.getEntity());
System.out.println("Status: " + response.getStatusLine());
System.out.println("Body: " + body);
}
}
}
Scraping Parallelo con ExecutorService e Pool di Proxy
Per elaborare migliaia di URL, devi parallelizzare con un pool di thread e ruotare i proxy per evitare rate limiting.
import okhttp3.Authenticator;
import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class ParallelScraper {
private static final String PROXY_HOST = "gate.proxyhat.com";
private static final int PROXY_PORT = 8080;
private static final String BASE_USER = "your-username";
private static final String PROXY_PASS = "your-password";
private static final int THREAD_POOL_SIZE = 20;
private static final int REQUEST_TIMEOUT_SECONDS = 30;
// Genera sessioni sticky uniche per IP dedicato per thread
private static String generateSessionId() {
return "sess-" + Long.toHexString(ThreadLocalRandom.current().nextLong());
}
// Factory per client con sessione dedicata
private static OkHttpClient createClient(String sessionId) {
String username = BASE_USER + "-country-US-session-" + sessionId;
Proxy proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress(PROXY_HOST, PROXY_PORT));
Authenticator auth = new Authenticator() {
@Override
public Request authenticate(Route route, Response response) {
if (response.responseCount() >= 3) return null;
return response.request().newBuilder()
.header("Proxy-Authorization", Credentials.basic(username, PROXY_PASS))
.build();
}
};
return new OkHttpClient.Builder()
.proxy(proxy)
.proxyAuthenticator(auth)
.connectTimeout(Duration.ofSeconds(15))
.readTimeout(Duration.ofSeconds(REQUEST_TIMEOUT_SECONDS))
.connectionPool(new okhttp3.ConnectionPool(5, 5, TimeUnit.MINUTES))
.retryOnConnectionFailure(true)
.build();
}
public static void main(String[] args) throws Exception {
// Lista di URL da processare
List<String> urls = List.of(
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/2",
"https://httpbin.org/ip",
"https://httpbin.org/headers",
"https://httpbin.org/user-agent"
);
// Pool di thread con naming per debug
ExecutorService executor = Executors.newFixedThreadPool(
THREAD_POOL_SIZE,
r -> {
Thread t = new Thread(r);
t.setName("scraper-" + t.getId());
t.setDaemon(true);
return t;
}
);
// Crea un client per ogni thread (sticky session)
ConcurrentHashMap<String, OkHttpClient> clientPool = new ConcurrentHashMap<>();
AtomicInteger success = new AtomicInteger(0);
AtomicInteger failed = new AtomicInteger(0);
// Task per ogni URL
List<CompletableFuture<Void>> futures = urls.stream()
.map(url -> CompletableFuture.runAsync(() -> {
String sessionId = generateSessionId();
OkHttpClient client = clientPool.computeIfAbsent(
sessionId, ParallelScraper::createClient);
Request request = new Request.Builder()
.url(url)
.header("User-Agent", "Mozilla/5.0")
.build();
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) {
System.out.printf("[%s] OK %d - %s%n",
sessionId.substring(0, 8),
response.code(),
url);
success.incrementAndGet();
} else {
System.out.printf("[%s] FAIL %d - %s%n",
sessionId.substring(0, 8),
response.code(), url);
failed.incrementAndGet();
}
} catch (Exception e) {
System.err.printf("[%s] ERROR - %s: %s%n",
sessionId.substring(0, 8), url, e.getMessage());
failed.incrementAndGet();
}
}, executor))
.toList();
// Attendi completamento
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
System.out.printf("%nCompletato: %d successi, %d fallimenti%n",
success.get(), failed.get());
}
}
Configurazione TLS e SSLContext Personalizzato
In alcuni scenari devi personalizzare il contesto SSL: certificati self-signed, TLS 1.3 forzato, o trust store personalizzato.
SSLContext con Trust Store Personalizzato
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.io.FileInputStream;
import java.net.http.HttpClient;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
public class TlsConfiguration {
// Crea SSLContext con trust store personalizzato
public static SSLContext createSSLContext(String trustStorePath, String password)
throws Exception {
KeyStore trustStore = KeyStore.getInstance("PKCS12");
try (FileInputStream fis = new FileInputStream(trustStorePath)) {
trustStore.load(fis, password.toCharArray());
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
return sslContext;
}
// Crea SSLContext permissivo (SOLO per sviluppo/testing!)
public static SSLContext createTrustAllSSLContext() throws Exception {
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
public void checkClientTrusted(X509Certificate[] certs, String t) { }
public void checkServerTrusted(X509Certificate[] certs, String t) { }
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new SecureRandom());
return sslContext;
}
public static void main(String[] args) throws Exception {
// Con Java 11+ HttpClient
SSLContext sslContext = createSSLContext("/path/to/truststore.p12", "changeit");
HttpClient client = HttpClient.newBuilder()
.sslContext(sslContext)
.build();
System.out.println("SSLContext configurato con TLS " + sslContext.getProtocol());
}
}
TLS con OkHttp
import okhttp3.OkHttpClient;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
import java.time.Duration;
public class OkHttpTlsExample {
public static void main(String[] args) throws Exception {
// Usa TLS 1.3 con cipher suites specifici
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(null, null, null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, getDefaultTrustManager())
.connectTimeout(Duration.ofSeconds(15))
.build();
System.out.println("OkHttp configurato con TLS 1.3");
}
private static X509TrustManager getDefaultTrustManager() throws Exception {
javax.net.ssl.TrustManagerFactory tmf =
javax.net.ssl.TrustManagerFactory.getInstance(
javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm());
tmf.init((java.security.KeyStore) null);
for (javax.net.ssl.TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
return (X509TrustManager) tm;
}
}
throw new IllegalStateException("Nessun X509TrustManager trovato");
}
}
Confronto tra Client HTTP Java
| Feature | Java 11+ HttpClient | OkHttp | Apache HttpClient | Jsoup |
|---|---|---|---|---|
| HTTP/2 Support | ✅ Nativo | ✅ Nativo | ⚠️ Parziale (5.x) | ❌ No |
| Async/Reactive | ✅ CompletableFuture | ✅ Call enqueue | ⚠️ Async in 5.x | ❌ Sync only |
| WebSocket | ✅ Nativo | ✅ Nativo | ❌ No | ❌ No |
| Connection Pool | ✅ Built-in | ✅ ConnectionPool | ✅ PoolingCM | ❌ No |
| Interceptors | ❌ No | ✅ Potenti | ✅ HttpRequestInterceptor | ❌ No |
| Proxy Auth | ✅ Authenticator | ✅ proxyAuthenticator | ✅ CredentialsProvider | ⚠️ Header manuale |
| HTML Parsing | ❌ No | ❌ No | ❌ No | ✅ Core feature |
| JDK Required | 11+ | 8+ | 8+ | 8+ |
| Android Ready | ❌ Solo Android 11+ | ✅ Sì | ✅ Sì | ✅ Sì |
Best Practices per Produzione
- Connection Pooling: Configura pool con limiti appropriati. OkHttp default (5 connessioni) può essere troppo basso per scraping intensivo.
- Timeout Gerarchici: Imposta connect < read < write timeout per evitare thread bloccati.
- Retry con Backoff: Implementa exponential backoff con jitter per evitare thundering herd.
- User-Agent Realistici: Ruota user-agent e header per simulare browser reali.
- Rate Limiting: Rispetta robots.txt e implementa throttling per non sovraccaricare i target.
- Logging Strutturato: Logga status code, latenza, IP proxy per debugging.
- Circuit Breaker: Disabilita proxy fallimentari temporaneamente per evitare cascata di errori.
Key Takeaways:
- Java 11+ HttpClient è la scelta moderna con supporto HTTP/2 nativo e API reattiva.
- OkHttp offre la massima flessibilità con interceptori e pooling avanzato.
- Jsoup è ideale per parsing HTML ma combina con un vero HTTP client per controllo completo.
- Usa sessioni sticky ProxyHat per mantenere IP consistenti durante operazioni multi-step.
- Implementa sempre retry con backoff esponenziale e circuit breaker per resilienza.
Per iniziare con proxy residenziali affidabili, visita ProxyHat pricing e ottieni accesso istantaneo a milioni di IP in 195+ paesi.






