Java-разработчики часто сталкиваются с задачей маршрутизации HTTP-запросов через прокси-серверы. Это может быть веб-скрейпинг, интеграция с API, требующими определённой геолокации, или корпоративные ограничения сети. В этом руководстве мы рассмотрим все популярные HTTP-клиенты в экосистеме Java и покажем, как правильно настроить работу с прокси.
Java 11+ HttpClient: ProxySelector и Authenticator
Модуль java.net.http появился в Java 11 и стал стандартом для новых проектов. Он поддерживает HTTP/2, WebSocket и асинхронные запросы. Для настройки прокси используются ProxySelector и Authenticator.
Базовая настройка прокси
import java.net.InetSocketAddress;
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;
public class JavaHttpClientProxyExample {
public static void main(String[] args) throws Exception {
// Создаём ProxySelector для HTTP-прокси
ProxySelector proxySelector = ProxySelector.of(
new InetSocketAddress("gate.proxyhat.com", 8080)
);
// Строим клиент с прокси и таймаутами
HttpClient client = HttpClient.newBuilder()
.proxy(proxySelector)
.connectTimeout(Duration.ofSeconds(30))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/ip"))
.timeout(Duration.ofSeconds(60))
.GET()
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("Status: " + response.statusCode());
System.out.println("Body: " + response.body());
}
}
Аутентификация на прокси
Residential-прокси обычно требуют авторизацию. В Java HttpClient используется Authenticator:
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
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;
public class AuthenticatedProxyExample {
private static final String PROXY_HOST = "gate.proxyhat.com";
private static final int PROXY_PORT = 8080;
private static final String USERNAME = "user-country-US";
private static final String PASSWORD = "your_password";
public static void main(String[] args) throws Exception {
// Authenticator для прокси-авторизации
Authenticator proxyAuth = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
// Проверяем, что запрос идёт к нашему прокси
if (getRequestorType() == RequestorType.PROXY) {
return new PasswordAuthentication(USERNAME, PASSWORD.toCharArray());
}
return null;
}
};
ProxySelector proxySelector = ProxySelector.of(
new InetSocketAddress(PROXY_HOST, PROXY_PORT)
);
HttpClient client = HttpClient.newBuilder()
.proxy(proxySelector)
.authenticator(proxyAuth)
.connectTimeout(Duration.ofSeconds(30))
.build();
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.body());
}
}
Асинхронные запросы через прокси
Для высокопроизводительных приложений используйте асинхронный API:
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
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.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
public class AsyncProxyExample {
private static final ExecutorService executor = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
Authenticator proxyAuth = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
"user-country-DE",
"your_password".toCharArray()
);
}
};
HttpClient client = HttpClient.newBuilder()
.proxy(ProxySelector.of(new InetSocketAddress("gate.proxyhat.com", 8080)))
.authenticator(proxyAuth)
.executor(executor)
.build();
List<String> urls = List.of(
"https://httpbin.org/ip",
"https://httpbin.org/headers",
"https://httpbin.org/user-agent"
);
List<CompletableFuture<HttpResponse<String>>> futures = urls.stream()
.map(url -> {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.GET()
.build();
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
})
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> {
futures.forEach(f -> {
try {
HttpResponse<String> response = f.get();
System.out.println(response.uri() + " -> " + response.statusCode());
} catch (Exception e) {
e.printStackTrace();
}
});
})
.join();
executor.shutdown();
}
}
OkHttp: Proxy и Authenticator
OkHttp от Square — один из самых популярных HTTP-клиентов для Android и серверных Java-приложений. Он предлагает продвинутые возможности: пул соединений, прозрачное сжатие GZIP, кэширование ответов и автоматический retry.
Базовая конфигурация с прокси
import okhttp3.Authenticator;
import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.time.Duration;
public class OkHttpProxyExample {
private static final String PROXY_HOST = "gate.proxyhat.com";
private static final int PROXY_PORT = 8080;
private static final String USERNAME = "user-country-US";
private static final String PASSWORD = "your_password";
public static void main(String[] args) throws IOException {
// Создаём объект Proxy
Proxy proxy = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress(PROXY_HOST, PROXY_PORT)
);
// Authenticator для прокси-авторизации
Authenticator proxyAuthenticator = new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
// Предотвращаем бесконечный цикл при неверных креденшалах
if (response.responseCount() >= 3) {
return null;
}
String credential = Credentials.basic(USERNAME, PASSWORD);
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
}
};
OkHttpClient client = new OkHttpClient.Builder()
.proxy(proxy)
.proxyAuthenticator(proxyAuthenticator)
.connectTimeout(Duration.ofSeconds(30))
.readTimeout(Duration.ofSeconds(60))
.writeTimeout(Duration.ofSeconds(60))
.retryOnConnectionFailure(true)
.build();
Request request = new Request.Builder()
.url("https://httpbin.org/ip")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println("Status: " + response.code());
System.out.println("Body: " + response.body().string());
}
}
}
Connection Pool и оптимизация
OkHttp автоматически управляет пулом соединений. Для максимальной производительности настройте параметры под вашу нагрузку:
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import java.util.concurrent.TimeUnit;
public class OptimizedOkHttpClient {
public static OkHttpClient createOptimizedClient() {
// ConnectionPool: max 100 idle connections, keep-alive 5 минут
ConnectionPool connectionPool = new ConnectionPool(100, 5, TimeUnit.MINUTES);
return new OkHttpClient.Builder()
.proxy(new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress("gate.proxyhat.com", 8080)
))
.proxyAuthenticator((route, response) -> {
String credential = Credentials.basic("user-country-US", "your_password");
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
})
.connectionPool(connectionPool)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
// Интервал для проверки живости соединения
.pingInterval(30, TimeUnit.SECONDS)
// Retry при ошибках сети
.retryOnConnectionFailure(true)
.build();
}
}
Jsoup: HTML-парсинг через прокси
Jsoup — библиотека для парсинга HTML, которая упрощает извлечение данных со веб-страниц. По умолчанию Jsoup использует HttpURLConnection, но его можно настроить на работу через прокси.
Jsoup с системным прокси
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
public class JsoupSystemProxyExample {
public static void main(String[] args) throws Exception {
// Устанавливаем системные свойства для прокси
System.setProperty("http.proxyHost", "gate.proxyhat.com");
System.setProperty("http.proxyPort", "8080");
System.setProperty("https.proxyHost", "gate.proxyhat.com");
System.setProperty("https.proxyPort", "8080");
// Authenticator для авторизации на прокси
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
"user-country-US",
"your_password".toCharArray()
);
}
});
Document doc = Jsoup.connect("https://example.com")
.timeout(30000)
.followRedirects(true)
.get();
String title = doc.title();
System.out.println("Page title: " + title);
// Извлекаем все ссылки
doc.select("a[href]").forEach(link -> {
System.out.println(link.attr("href") + " -> " + link.text());
});
}
}
Jsoup с OkHttp как backend
Для production-решений лучше использовать OkHttp как HTTP-backend для Jsoup. Это даёт контроль над пулом соединений, retry-политикой и таймаутами:
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 JsoupOkHttpIntegration {
private final OkHttpClient httpClient;
public JsoupOkHttpIntegration(String proxyUser, String proxyPass) {
Proxy proxy = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress("gate.proxyhat.com", 8080)
);
this.httpClient = new OkHttpClient.Builder()
.proxy(proxy)
.proxyAuthenticator((route, response) -> {
String credential = okhttp3.Credentials.basic(proxyUser, proxyPass);
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
})
.build();
}
public Document fetchAndParse(String url) throws Exception {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new RuntimeException("HTTP " + response.code() + ": " + response.message());
}
String html = response.body().string();
return Jsoup.parse(html, url);
}
}
public static void main(String[] args) throws Exception {
JsoupOkHttpIntegration scraper = new JsoupOkHttpIntegration(
"user-country-DE",
"your_password"
);
Document doc = scraper.fetchAndParse("https://news.ycombinator.com");
doc.select(".titleline > a").forEach(link -> {
System.out.println(link.text() + " -> " + link.attr("href"));
});
}
}
Apache HttpClient: краткий обзор
Apache HttpClient (особенно версия 4.x) всё ещё широко используется в enterprise-проектах. Для настройки прокси требуется HttpHost и CredentialsProvider:
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
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 java.net.Authenticator;
public class ApacheHttpClientProxyExample {
public static void main(String[] args) throws Exception {
// Connection Pool
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(200);
cm.setDefaultMaxPerRoute(50);
// Прокси-хост
HttpHost proxy = new HttpHost("gate.proxyhat.com", 8080);
// Credentials для авторизации
BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new org.apache.http.auth.AuthScope(proxy),
new org.apache.http.auth.UsernamePasswordCredentials(
"user-country-US",
"your_password"
)
);
// RequestConfig с прокси
RequestConfig config = RequestConfig.custom()
.setProxy(proxy)
.setConnectTimeout(30000)
.setSocketTimeout(60000)
.build();
try (CloseableHttpClient client = HttpClients.custom()
.setConnectionManager(cm)
.setDefaultCredentialsProvider(credsProvider)
.setDefaultRequestConfig(config)
.build()) {
HttpGet request = new HttpGet("https://httpbin.org/ip");
try (CloseableHttpResponse response = client.execute(request)) {
System.out.println("Status: " + response.getStatusLine());
String body = new String(
response.getEntity().getContent().readAllBytes()
);
System.out.println("Body: " + body);
}
}
}
}
Параллельный скрейпинг с пулом residential-прокси
Для массового сбора данных необходимо распараллелить запросы и использовать ротацию IP. Residential-прокси идеально подходят для этой задачи, так как они выглядят как обычные пользователи.
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
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.*;
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 = "user";
private static final String PASSWORD = "your_password";
// Список стран для ротации
private static final List<String> COUNTRIES = List.of(
"US", "DE", "GB", "FR", "JP", "AU", "CA", "BR"
);
private final ExecutorService executor;
private final HttpClient sharedClient;
private final AtomicInteger counter = new AtomicInteger(0);
public ParallelScraper(int threadCount) {
this.executor = Executors.newFixedThreadPool(threadCount);
// Базовый клиент без аутентификации (добавим per-request)
this.sharedClient = HttpClient.newBuilder()
.proxy(ProxySelector.of(new InetSocketAddress(PROXY_HOST, PROXY_PORT)))
.connectTimeout(Duration.ofSeconds(30))
.executor(executor)
.build();
}
private Authenticator createAuthenticator(String country) {
String username = BASE_USER + "-country-" + country;
return new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, PASSWORD.toCharArray());
}
};
}
public CompletableFuture<ScrapeResult> scrape(String url, String country) {
long startTime = System.currentTimeMillis();
int requestId = counter.incrementAndGet();
// Создаём клиент с аутентификацией для конкретной страны
HttpClient client = HttpClient.newBuilder()
.proxy(ProxySelector.of(new InetSocketAddress(PROXY_HOST, PROXY_PORT)))
.authenticator(createAuthenticator(country))
.connectTimeout(Duration.ofSeconds(30))
.executor(executor)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(60))
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.GET()
.build();
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(response -> {
long duration = System.currentTimeMillis() - startTime;
return new ScrapeResult(
requestId,
url,
country,
response.statusCode(),
response.body(),
duration
);
})
.exceptionally(e -> {
long duration = System.currentTimeMillis() - startTime;
return new ScrapeResult(
requestId,
url,
country,
-1,
"Error: " + e.getMessage(),
duration
);
});
}
public void scrapeAll(List<String> urls) throws Exception {
List<CompletableFuture<ScrapeResult>> futures = new java.util.ArrayList<>();
for (int i = 0; i < urls.size(); i++) {
String url = urls.get(i);
String country = COUNTRIES.get(i % COUNTRIES.size());
// Добавляем задержку для избежания rate limiting
Thread.sleep(100);
futures.add(scrape(url, country));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.join();
// Выводим результаты
futures.forEach(f -> {
try {
ScrapeResult result = f.get();
System.out.printf(
"[%d] %s (%s) -> %d (%dms)%n",
result.requestId(),
result.url(),
result.country(),
result.statusCode(),
result.duration()
);
} catch (Exception e) {
e.printStackTrace();
}
});
executor.shutdown();
}
public record ScrapeResult(
int requestId,
String url,
String country,
int statusCode,
String body,
long duration
) {}
public static void main(String[] args) throws Exception {
ParallelScraper scraper = new ParallelScraper(10);
List<String> urls = List.of(
"https://httpbin.org/ip",
"https://httpbin.org/headers",
"https://httpbin.org/user-agent",
"https://httpbin.org/ip",
"https://httpbin.org/headers"
);
scraper.scrapeAll(urls);
}
}
TLS и кастомный SSLContext
При работе с прокси могут возникать TLS-особенности. Некоторые прокси используют собственные сертификаты для инспекции трафика (MITM). В таких случаях может потребоваться кастомный SSLContext.
Пропуск верификации сертификата (только для разработки!)
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.net.http.HttpClient;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
public class InsecureTlsExample {
// ВНИМАНИЕ: Используйте только для тестирования!
// В production это создаёт уязвимость безопасности.
public static HttpClient createInsecureClient() throws Exception {
// TrustManager, который принимает все сертификаты
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new SecureRandom());
return HttpClient.newBuilder()
.sslContext(sslContext)
.proxy(ProxySelector.of(new InetSocketAddress("gate.proxyhat.com", 8080)))
.build();
}
}
Правильный подход: кастомный TrustStore
Для production лучше добавить сертификат прокси в custom TrustStore:
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.net.http.HttpClient;
import java.security.KeyStore;
import java.security.SecureRandom;
public class CustomTrustStoreExample {
public static HttpClient createClientWithCustomTrustStore(
String trustStorePath,
String trustStorePassword
) throws Exception {
// Загружаем кастомный TrustStore
KeyStore trustStore = KeyStore.getInstance("JKS");
try (FileInputStream fis = new FileInputStream(trustStorePath)) {
trustStore.load(fis, trustStorePassword.toCharArray());
}
// Создаём TrustManagerFactory
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
);
tmf.init(trustStore);
// Создаём SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
return HttpClient.newBuilder()
.sslContext(sslContext)
.proxy(ProxySelector.of(new InetSocketAddress("gate.proxyhat.com", 8080)))
.connectTimeout(java.time.Duration.ofSeconds(30))
.build();
}
}
Сравнение HTTP-клиентов для работы с прокси
| Критерий | Java 11+ HttpClient | OkHttp | Apache HttpClient 4 | Jsoup (нативный) |
|---|---|---|---|---|
| Поддержка HTTP/2 | Да | Да | Нет (только HTTP/1.1) | Нет |
| Асинхронный API | Да (CompletableFuture) | Да (через enqueue) | Нет (блокирующий) | Нет |
| Настройка прокси | ProxySelector + Authenticator | Proxy + Authenticator | HttpHost + CredentialsProvider | Системные свойства |
| Connection Pool | Встроенный | Встроенный (ConnectionPool) | PoolingHttpClientConnectionManager | Нет |
| Retry-политика | Ручная реализация | Встроенная | Через HttpRequestRetryHandler | Нет |
| Требует внешних зависимостей | Нет (JDK) | Да | Да | Да (только Jsoup) |
| Идеален для | Новые проекты, JDK-only | Android, production | Legacy системы | Простой парсинг |
Best Practices для production
- Всегда устанавливайте таймауты — connect timeout, read timeout, write timeout. Без них приложение может зависнуть бесконечно.
- Используйте connection pooling — переиспользование TCP-соединений значительно ускоряет работу при множественных запросах.
- Реализуйте exponential backoff для retry — при ошибках сети не пытайтесь переподключиться мгновенно.
- Логируйте все ошибки — включайте URL, статус-код, время ответа и страну/сессию прокси.
- Ротируйте сессии прокси — для大规模 скрейпинга используйте параметр
-session-XXXв username для sticky sessions. - Уважайте robots.txt и ToS — проверяйте, разрешён ли скрейпинг на целевом сайте.
Совет: Для residential-прокси используйте формат username
user-country-US-session-abc123. Это создаст «липкую» сессию — все запросы в рамках сессии будут идти с одного IP, что важно для авторизованных сессий и корзин покупок.
Key Takeaways
- Java 11+ HttpClient — нативное решение без зависимостей, подходит для новых проектов. Используйте
ProxySelector.of()иAuthenticatorдля аутентификации. - OkHttp — лучший выбор для production: встроенный connection pool, retry-механизмы, отличная производительность. Настройте
ProxyиproxyAuthenticator. - Jsoup удобен для HTML-парсинга, но для HTTP-запросов лучше интегрировать его с OkHttp для контроля над соединениями.
- Всегда настраивайте таймауты и connection pooling — это критично для производительности.
- Для параллельного скрейпинга используйте
CompletableFutureилиExecutorServiceс ротацией стран/сессий прокси. - TLS-особенности прокси решаются через кастомный
SSLContextили TrustStore.
Готовы начать работу с прокси? Зарегистрируйтесь на ProxyHat и получите доступ к residential, mobile и datacenter-прокси в более чем 190 странах. Подробную документацию по API и примеры кода на Java вы найдёте в нашем разделе блога.






