Java HTTP Proxy: 왜 중요한가?
현대 웹 애플리케이션과 데이터 파이프라인에서 HTTP 프록시는 선택이 아닌 필수입니다. IP 기반 속도 제한을 우회하거나, 지역별 콘텐츠에 접근하거나, 대규모 스크래핑 작업에서 차단을 방지하려면 프록시가 필요합니다.
Java 생태계에서는 여러 HTTP 클라이언트가 프록시를 지원하지만, 각각 설정 방식과 기능이 다릅니다. 이 가이드에서는 Java 11+ HttpClient, OkHttp, Jsoup을 중심으로 실무에서 즉시 적용 가능한 코드를 제공합니다.
Java 11+ HttpClient: ProxySelector와 Authenticator
Java 11부터 도입된 java.net.http.HttpClient는 현대적인 비동기 HTTP 클라이언트입니다. ProxySelector를 통해 프록시를 설정하고, Authenticator로 인증을 처리합니다.
기본 프록시 설정
ProxySelector를 사용해 모든 요청에 프록시를 적용할 수 있습니다:
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 JavaHttpClientProxy {
private static final String PROXY_HOST = "gate.proxyhat.com";
private static final int PROXY_PORT = 8080;
public static void main(String[] args) throws Exception {
// ProxySelector 생성
ProxySelector proxySelector = ProxySelector.of(
new InetSocketAddress(PROXY_HOST, PROXY_PORT)
);
// HttpClient 빌더에 프록시 적용
HttpClient client = HttpClient.newBuilder()
.proxy(proxySelector)
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
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());
}
}
인증 프록시: Authenticator 설정
ProxyHat과 같은 상용 프록시 서비스는 사용자 인증이 필요합니다. Authenticator를 사용해 Basic 인증을 구현합니다:
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 JavaHttpClientAuthProxy {
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 proxyAuthenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(USERNAME, PASSWORD.toCharArray());
}
};
ProxySelector proxySelector = ProxySelector.of(
new InetSocketAddress(PROXY_HOST, PROXY_PORT)
);
HttpClient client = HttpClient.newBuilder()
.proxy(proxySelector)
.authenticator(proxyAuthenticator)
.connectTimeout(Duration.ofSeconds(15))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/ip"))
.header("User-Agent", "ProxyHat-Java-Client/1.0")
.GET()
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("Response: " + response.body());
}
}
프록시 인증 팁: ProxyHat에서는 사용자 이름에
country-US,city-newyork,session-abc123같은 플래그를 포함해 지역 타겟팅과 스티키 세션을 제어할 수 있습니다.
비동기 요청과 병렬 처리
HttpClient.sendAsync()를 사용하면 논블로킹 비동기 요청이 가능합니다:
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.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
public class JavaHttpClientAsync {
private static final String PROXY_HOST = "gate.proxyhat.com";
private static final int PROXY_PORT = 8080;
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
Authenticator auth = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("user-country-US", "password".toCharArray());
}
};
HttpClient client = HttpClient.newBuilder()
.proxy(ProxySelector.of(new InetSocketAddress(PROXY_HOST, PROXY_PORT)))
.authenticator(auth)
.executor(executor)
.connectTimeout(Duration.ofSeconds(10))
.build();
List<String> urls = List.of(
"https://httpbin.org/ip",
"https://httpbin.org/headers",
"https://httpbin.org/user-agent"
);
List<CompletableFuture<String>> futures = urls.stream()
.map(url -> fetchAsync(client, url))
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> System.out.println("All requests completed"))
.join();
executor.shutdown();
}
private static CompletableFuture<String> fetchAsync(HttpClient client, String url) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(30))
.GET()
.build();
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(response -> {
System.out.println(url + " -> " + response.statusCode());
return response.body();
})
.exceptionally(ex -> {
System.err.println("Error fetching " + url + ": " + ex.getMessage());
return null;
});
}
}
OkHttp: 프록시와 인증 설정
OkHttp는 Square사에서 개발한 널리 사용되는 HTTP 클라이언트입니다. 연결 풀링, 인터셉터, 캐싱 등 풍부한 기능을 제공합니다.
기본 프록시 설정
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;
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 USERNAME = "user-country-DE";
private static final String PASSWORD = "your_password";
public static void main(String[] args) throws IOException {
// HTTP 프록시 정의
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) {
if (response.request().header("Proxy-Authorization") != null) {
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(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
Request request = new Request.Builder()
.url("https://httpbin.org/ip")
.header("User-Agent", "ProxyHat-OkHttp/1.0")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println("Status: " + response.code());
System.out.println("Body: " + response.body().string());
}
}
}
SOCKS5 프록시 사용
ProxyHat은 SOCKS5 프록시도 지원합니다. 포트 1080을 사용합니다:
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.net.InetSocketAddress;
import java.net.Proxy;
public class OkHttpSocks5Proxy {
public static void main(String[] args) throws Exception {
Proxy socksProxy = new Proxy(
Proxy.Type.SOCKS,
new InetSocketAddress("gate.proxyhat.com", 1080)
);
OkHttpClient client = new OkHttpClient.Builder()
.proxy(socksProxy)
.proxyAuthenticator((route, response) -> {
String credential = okhttp3.Credentials.basic("user-country-JP", "password");
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
})
.build();
Request request = new Request.Builder()
.url("https://httpbin.org/ip")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}
}
}
연결 풀링과 재시도 정책
OkHttp는 기본적으로 연결 풀을 관리합니다. 대규모 스크래핑에서는 풀 크기와 재시도 정책을 조정해야 합니다:
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import java.util.concurrent.TimeUnit;
public class OkHttpConnectionPool {
public static OkHttpClient createOptimizedClient() {
// 연결 풀: 최대 50개 연결, 5분간 유지
ConnectionPool connectionPool = new ConnectionPool(50, 5, TimeUnit.MINUTES);
return new OkHttpClient.Builder()
.connectionPool(connectionPool)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
// 추가 재시도 로직은 Interceptor로 구현
.build();
}
}
Jsoup: HTML 파싱과 프록시
Jsoup은 HTML 파싱에 특화된 라이브러리입니다. 내부적으로 Java의 HttpURLConnection을 사용하지만, 프록시 설정을 지원합니다.
Jsoup에서 프록시 설정
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
public class JsoupProxyExample {
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 설정 (Jsoup은 HttpURLConnection 사용)
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
if (getRequestingHost().equals(PROXY_HOST)) {
return new PasswordAuthentication(USERNAME, PASSWORD.toCharArray());
}
return null;
}
});
Proxy proxy = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress(PROXY_HOST, PROXY_PORT)
);
// Jsoup 연결에 프록시 적용
Document doc = Jsoup.connect("https://example.com")
.proxy(proxy)
.userAgent("ProxyHat-Jsoup/1.0")
.timeout(30000)
.followRedirects(true)
.get();
// HTML 파싱 예시
String title = doc.title();
System.out.println("Title: " + title);
// 링크 추출
for (Element link : doc.select("a[href]")) {
System.out.println("Link: " + link.attr("href") + " - " + link.text());
}
}
}
Jsoup + OkHttp 조합
Jsoup의 단순한 HTTP 기능보다 OkHttp의 강력한 기능이 필요하다면, OkHttp로 HTML을 가져와 Jsoup으로 파싱하는 방식을 권장합니다:
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 JsoupOkHttpCombo {
public static void main(String[] args) throws Exception {
OkHttpClient client = new OkHttpClient.Builder()
.proxy(new Proxy(Proxy.Type.HTTP,
new InetSocketAddress("gate.proxyhat.com", 8080)))
.proxyAuthenticator((route, response) -> {
String cred = okhttp3.Credentials.basic("user-country-GB", "password");
return response.request().newBuilder()
.header("Proxy-Authorization", cred)
.build();
})
.build();
Request request = new Request.Builder()
.url("https://news.ycombinator.com")
.build();
try (Response response = client.newCall(request).execute()) {
String html = response.body().string();
Document doc = Jsoup.parse(html, response.request().url().toString());
// 뉴스 제목 추출
doc.select(".titleline > a").forEach(el -> {
System.out.println("- " + el.text());
});
}
}
}
Apache HttpClient: 레거시 시스템 지원
많은 엔터프라이즈 시스템이 여전히 Apache HttpClient를 사용합니다. Apache HttpClient 4.x에서 프록시를 설정하는 방법입니다:
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.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.util.EntityUtils;
public class ApacheHttpClientProxy {
public static void main(String[] args) throws Exception {
HttpHost proxy = new HttpHost("gate.proxyhat.com", 8080);
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope(proxy),
new UsernamePasswordCredentials("user-country-FR", "password")
);
RequestConfig config = RequestConfig.custom()
.setProxy(proxy)
.setConnectTimeout(15000)
.setSocketTimeout(30000)
.build();
try (CloseableHttpClient client = HttpClients.custom()
.setDefaultCredentialsProvider(credsProvider)
.setDefaultRequestConfig(config)
.build()) {
HttpGet request = new HttpGet("https://httpbin.org/ip");
org.apache.http.HttpResponse response = client.execute(request);
String body = EntityUtils.toString(response.getEntity());
System.out.println(body);
}
}
}
병렬 스크래핑: Executors와 프록시 풀
대규모 데이터 수집에서는 여러 프록시 IP를 사용해 병렬로 요청을 처리해야 합니다. ProxyHat의 residential proxy pool과 ExecutorService를 결합한 예시입니다:
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
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-country-US-session-";
private static final String PASSWORD = "your_password";
private static final AtomicInteger sessionCounter = new AtomicInteger(0);
public static void main(String[] args) throws Exception {
List<String> urls = List.of(
"https://httpbin.org/ip",
"https://httpbin.org/headers",
"https://httpbin.org/user-agent",
"https://httpbin.org/get",
"https://httpbin.org/uuid"
);
int threadPoolSize = 10;
ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
// 각 URL에 대해 별도 세션으로 요청
List<Callable<String>> tasks = new ArrayList<>();
for (String url : urls) {
tasks.add(() -> fetchWithProxy(url));
}
List<Future<String>> futures = executor.invokeAll(tasks, 60, TimeUnit.SECONDS);
for (Future<String> future : futures) {
try {
String result = future.get();
System.out.println("Result: " + result.substring(0, Math.min(100, result.length())));
} catch (Exception e) {
System.err.println("Task failed: " + e.getMessage());
}
}
executor.shutdown();
}
private static String fetchWithProxy(String url) throws Exception {
// 각 요청마다 고유 세션 ID 생성 (다른 IP 할당)
String sessionId = "sess" + sessionCounter.incrementAndGet();
String username = BASE_USER + sessionId;
Proxy proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress(PROXY_HOST, PROXY_PORT));
OkHttpClient client = new OkHttpClient.Builder()
.proxy(proxy)
.proxyAuthenticator((route, response) -> {
String cred = okhttp3.Credentials.basic(username, PASSWORD);
return response.request().newBuilder()
.header("Proxy-Authorization", cred)
.build();
})
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url(url)
.header("User-Agent", "ProxyHat-Parallel/1.0")
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
}
세션 관리: ProxyHat에서
session-xxx플래그를 사용하면 동일한 세션 ID에 대해 고정 IP를 할당받을 수 있습니다. 여러 요청이 동일한 출발지 IP를 사용해야 할 때 유용합니다.
TLS와 SSLContext 설정
일부 프록시 환경에서는 TLS 설정이 필요합니다. 특히 자체 서명 인증서나 비표준 TLS 구성을 사용하는 경우 SSLContext를 커스터마이징해야 합니다.
커스텀 SSLContext (Java 11+ HttpClient)
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
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.security.SecureRandom;
import java.security.cert.X509Certificate;
public class TlsCustomContext {
public static void main(String[] args) throws Exception {
// 주의: 프로덕션에서는 신중하게 사용 (인증서 검증 비활성화)
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{
new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
}
}, new SecureRandom());
HttpClient client = HttpClient.newBuilder()
.proxy(ProxySelector.of(new InetSocketAddress("gate.proxyhat.com", 8080)))
.sslContext(sslContext)
.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());
}
}
OkHttp에서 TLS 설정
import okhttp3.OkHttpClient;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
public class OkHttpTlsConfig {
public static OkHttpClient createTrustAllClient() throws Exception {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{
new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
}
}, new SecureRandom());
return new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(),
(X509TrustManager) new TrustManager[]{
new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] c, String t) {}
public void checkServerTrusted(X509Certificate[] c, String t) {}
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
}
}[0])
.hostnameVerifier((hostname, session) -> true)
.build();
}
}
라이브러리 비교
| 특징 | Java 11+ HttpClient | OkHttp | Jsoup | Apache HttpClient |
|---|---|---|---|---|
| 비동기 지원 | ✅ 네이티브 | ❌ (별도 Interceptor 필요) | ❌ | ❌ (4.x) |
| 연결 풀링 | ✅ 내장 | ✅ ConnectionPool | ❌ | ✅ PoolingHttpClient |
| 프록시 인증 | Authenticator | proxyAuthenticator | Authenticator (전역) | CredentialsProvider |
| HTTP/2 | ✅ | ✅ | ❌ | ❌ (4.x) |
| HTML 파싱 | ❌ | ❌ | ✅ 강력 | ❌ |
| 최소 Java 버전 | Java 11 | Java 8+ | Java 8+ | Java 8+ |
Key Takeaways
- Java 11+ HttpClient는 네이티브 비동기 지원과 HTTP/2를 제공하며,
ProxySelector와Authenticator로 프록시 설정을 깔끔하게 처리합니다. - OkHttp는 강력한 연결 풀링, 인터셉터, 재시도 로직을 제공하며,
proxyAuthenticator로 인증을 처리합니다. - Jsoup은 HTML 파싱에 특화되어 있으며, 단순한 스크래핑에는 내장 HTTP를, 복잡한 경우는 OkHttp와 조합해 사용합니다.
- 병렬 스크래핑에서는
ExecutorService와 세션 기반 프록시 할당을 조합해 IP 회전과 속도 제한을 관리합니다. - TLS 설정이 필요한 경우
SSLContext를 커스터마이징하되, 프로덕션에서는 보안 영향을 신중히 검토해야 합니다. - ProxyHat 사용자 이름에
country-XX,city-xxx,session-xxx플래그를 포함해 지역 타겟팅과 스티키 세션을 제어할 수 있습니다.
결론
Java에서 HTTP 프록시를 사용하는 것은 복잡하지 않습니다. 각 라이브러리의 특성에 맞는 설정 방식을 이해하고, 연결 풀링, 타임아웃, 재시도 정책을 적절히 구성하면 안정적인 스크래핑 파이프라인을 구축할 수 있습니다.
ProxyHat과 같은 residential proxy 서비스를 사용하면 IP 차단 없이 대규모 데이터 수집이 가능합니다. 지금 ProxyHat 플랜을 확인하고, Java 프로젝트에 프록시를 통합해 보세요.






