企業向け Java アプリケーションは、外部 API に定期的にアクセスしたり、データを解析したり、隔離された企業ネットワークで動作したりします。そして、これらの各ケースでは、早かれ遅かれ、プロキシサーバーを介してトラフィックをルーティングする必要があります。Java でのプロキシ設定は簡単なタスクのように見えますが、RestTemplate、OkHttp、WebClient が異なる動作をし、JVM のシステムプロパティがすべての場所で機能するわけではないことに直面すると、そうではありません。このガイドでは、具体的なコード例、落とし穴の説明、およびさまざまなタスクに適したプロキシの種類を選択するための推奨事項を提供します。
Java アプリケーションにプロキシが必要な理由
Java は企業開発の主要な言語の一つです。Spring Boot サービス、マイクロサービスアーキテクチャ、バッチタスク、ETL パイプライン — これらはすべて定期的に外部世界に HTTP リクエストを行います。そして、これらのシナリオのそれぞれで、プロキシは特定のタスクを解決します:
- 必須のプロキシゲートを持つ企業ネットワーク。 多くの企業では、すべてのアウトバウンドトラフィックが企業プロキシ (Squid、Zscaler、BlueCoat) を通過します。これを設定しないと、アプリケーションはインターネットに接続できません。
- データの解析と収集。 外部サイトからデータを収集するサービス (価格、見積もり、ニュース) は、レート制限や IP によるブロックを回避するためにプロキシを使用します。
- ジオテスト。 QA チームは、API やサイトが異なる地域でどのように機能するかを確認します — プロキシは、必要な国からのリクエストをエミュレートすることを可能にします。
- ターゲット API 側の制限を回避。 一部の外部 API には、1 つの IP からのリクエスト数に制限があります。プロキシのローテーションは、負荷を分散させることができます。
- セキュリティと匿名化。 外部サービスにアクセスする際にアプリケーションの実際の IP を隠すことは、センシティブな統合における標準的なプラクティスです。
タスクに応じて、Java におけるプロキシ設定のアプローチは大きく異なります。JVM のグローバルシステムプロパティは最も簡単なオプションですが、一部の HTTP クライアントでは機能しません。OkHttp と WebClient は明示的な設定を必要とします。それぞれのケースを順に見ていきましょう。
JVM のシステムプロパティによるグローバル設定
Java アプリケーションのすべての HTTP トラフィックをプロキシ経由でルーティングする最も簡単な方法は、JVM のシステムプロパティを使用することです。これは、標準の HttpURLConnection と、それを内部で使用する多くのライブラリに対して機能します。
プロキシを設定する方法はいくつかあります:
1. JVM の起動時の引数を通じて:
java -Dhttp.proxyHost=proxy.example.com \
-Dhttp.proxyPort=8080 \
-Dhttps.proxyHost=proxy.example.com \
-Dhttps.proxyPort=8080 \
-Dhttp.nonProxyHosts="localhost|127.0.0.1|*.internal.corp" \
-jar myapp.jar
2. アプリケーションコードを通じて (例えば、main メソッドや @PostConstruct 内):
System.setProperty("http.proxyHost", "proxy.example.com");
System.setProperty("http.proxyPort", "8080");
System.setProperty("https.proxyHost", "proxy.example.com");
System.setProperty("https.proxyPort", "8080");
// 除外 — プロキシを通過しないホスト
System.setProperty("http.nonProxyHosts", "localhost|127.0.0.1|*.internal.corp");
3. Spring Boot の application.properties ファイルを通じて (その後、@PostConstruct を通じて適用):
# application.properties proxy.host=proxy.example.com proxy.port=8080 proxy.username=user proxy.password=secret
⚠️ 知っておくべきこと
JVM のシステムプロパティは HttpURLConnection にのみ機能します。OkHttp、Apache HttpClient、および Reactor Netty (WebClient) はこれらのプロパティを無視します — それらには、以下に説明する明示的な設定が必要です。
プロキシが認証を必要とする場合は、Authenticator を使用します:
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
if (getRequestorType() == RequestorType.PROXY) {
return new PasswordAuthentication("username", "password".toCharArray());
}
return null;
}
});
Spring Boot のプロキシ: RestTemplate と Apache HttpClient
RestTemplate は Spring の標準的な同期 HTTP クライアントです。デフォルトでは、SimpleClientHttpRequestFactory を使用し、これは HttpURLConnection に依存し、JVM のシステムプロパティを読み取ります。しかし、より柔軟な設定 (タイムアウト、接続プール、認証付きプロキシ) を行うには、Apache HttpClient をバックエンドとして使用することをお勧めします。
Maven の依存関係:
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3.1</version>
</dependency>
Apache HttpClient 5 を使用したプロキシを介した RestTemplate の設定:
@Configuration
public class RestTemplateConfig {
@Value("${proxy.host}")
private String proxyHost;
@Value("${proxy.port}")
private int proxyPort;
@Value("${proxy.username:}")
private String proxyUsername;
@Value("${proxy.password:}")
private String proxyPassword;
@Bean
public RestTemplate restTemplate() {
HttpHost proxy = new HttpHost("http", proxyHost, proxyPort);
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
if (!proxyUsername.isEmpty()) {
credentialsProvider.setCredentials(
new AuthScope(proxyHost, proxyPort),
new UsernamePasswordCredentials(proxyUsername, proxyPassword.toCharArray())
);
}
CloseableHttpClient httpClient = HttpClients.custom()
.setProxy(proxy)
.setDefaultCredentialsProvider(credentialsProvider)
.build();
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setConnectTimeout(5000);
factory.setReadTimeout(10000);
return new RestTemplate(factory);
}
}
この設定により、プロキシのパラメータを application.properties または環境変数に移動でき、異なるプロキシを持つ prod/staging/dev 環境にとって重要です。
一部のリクエストをプロキシ経由で、他のリクエストを直接行う必要がある場合は、異なる名前の 2 つの独立した RestTemplate ビーンを作成し、必要なものをインジェクションするために @Qualifier を使用します。
Spring WebClient のプロキシ (リアクティブスタック)
WebClient は、Reactor Netty に基づく Spring のリアクティブ HTTP クライアントです。プロキシに対して JVM のシステムプロパティを読み取らず、クライアントを作成する際に ProxyProvider を介して設定します。
依存関係 (spring-boot-starter-webflux に含まれています):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
HTTP プロキシを使用した WebClient の設定:
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
HttpClient httpClient = HttpClient.create()
.proxy(proxy -> proxy
.type(ProxyProvider.Proxy.HTTP)
.host("proxy.example.com")
.port(8080)
.username("user")
.password(user -> "password")
);
ClientHttpConnector connector =
new ReactorClientHttpConnector(httpClient);
return WebClient.builder()
.clientConnector(connector)
.build();
}
}
WebClient での SOCKS5 プロキシの場合:
HttpClient httpClient = HttpClient.create()
.proxy(proxy -> proxy
.type(ProxyProvider.Proxy.SOCKS5)
.host("socks5.proxy.example.com")
.port(1080)
.username("user")
.password(user -> "password")
);
💡 設定のヒント
リアクティブアプリケーションでは、異なる外部サービス用に異なるプロキシを持つ複数の WebClient インスタンスを作成する必要があることがよくあります。WebClient.Builder をプロトタイプビーンとして使用し、.mutate() を介して設定をオーバーライドします。
OkHttp のプロキシ設定
OkHttp は、多くの Java および Android アプリケーション、さらには一部の Spring 統合 (例えば、OkHttp バックエンドを持つ Feign) で使用される人気の HTTP ライブラリです。独自のプロキシ設定メカニズムを持ち、JVM のシステムプロパティを読み取ることはありません。
OkHttp における HTTP/HTTPS プロキシの基本設定:
import okhttp3.*;
import java.net.InetSocketAddress;
import java.net.Proxy;
// プロキシを作成
Proxy proxy = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress("proxy.example.com", 8080)
);
// プロキシ認証
Authenticator proxyAuthenticator = (route, response) -> {
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(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
// リクエストの例
Request request = new Request.Builder()
.url("https://api.example.com/data")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}
Spring Boot との OkHttp の統合:
@Configuration
@ConditionalOnProperty(name = "proxy.enabled", havingValue = "true")
public class OkHttpConfig {
@Value("${proxy.host}")
private String proxyHost;
@Value("${proxy.port}")
private int proxyPort;
@Bean
public OkHttpClient okHttpClient() {
Proxy proxy = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress(proxyHost, proxyPort)
);
return new OkHttpClient.Builder()
.proxy(proxy)
.connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES))
.build();
}
}
@ConditionalOnProperty に注意してください — これにより、コードを変更することなく、必要な環境でのみプロキシを有効にできます。
データを解析するために OkHttp を レジデンシャルプロキシ と一緒に使用する場合は、接続プールを正しく設定することが重要です。レジデンシャルプロキシは、データセンターのプロキシと比較してレイテンシが高くなることが多いため、タイムアウトを増やし、プールのサイズを減らして、利用可能な接続が枯渇しないようにします。
Java における SOCKS5 プロキシ: 特徴と例
SOCKS5 は、HTTP プロキシと比較して低レベルのプロトコルです。これはトランスポート層で動作し、任意のプロトコル (HTTP、HTTPS、FTP など) をサポートし、プロキシ側での DNS 解決もサポートします。Java アプリケーションにおいて、SOCKS5 は HTTP だけでなく他の TCP 接続をプロキシする必要がある場合に特に便利です。
JVM のシステムプロパティを介した SOCKS5:
System.setProperty("socksProxyHost", "socks5.proxy.example.com");
System.setProperty("socksProxyPort", "1080");
System.setProperty("java.net.socks.username", "user");
System.setProperty("java.net.socks.password", "password");
OkHttp における SOCKS5 (java.net.Proxy を介して):
Proxy socks5Proxy = new Proxy(
Proxy.Type.SOCKS,
new InetSocketAddress("socks5.proxy.example.com", 1080)
);
OkHttpClient client = new OkHttpClient.Builder()
.proxy(socks5Proxy)
.build();
SOCKS5 と DNS の重要な特徴: デフォルトでは、Java はローカルで DNS を解決し、プロキシを介して IP アドレスを送信します。これにより、実際の DNS リクエストが漏洩する可能性があります。DNS 解決もプロキシを介して行うには、次のアプローチを使用して OkHttp を設定します:
// SOCKS5 を介しての解決を強制するカスタム DNS
OkHttpClient client = new OkHttpClient.Builder()
.proxy(socks5Proxy)
.dns(hostname -> {
// 解決されていないホストを返す — OkHttp がそれをプロキシに渡します
return Collections.singletonList(InetAddress.getByName(hostname));
})
.build();
SOCKS5 と HTTP プロキシの選択時期
| 基準 | HTTP プロキシ | SOCKS5 プロキシ |
|---|---|---|
| プロトコル | HTTP/HTTPS | 任意の TCP/UDP |
| プロキシを介した DNS | 実装による | サポートされています |
| Java との互換性 | ネイティブ | ネイティブ (java.net.Proxy.Type.SOCKS) |
| 使用法 | 企業ゲートウェイ、ウェブパース | 匿名化、非標準プロトコル |
Java アプリケーションにおけるプロキシのローテーション
アプリケーションが同じリソースに対して大量のリクエストを行う場合、単一のプロキシ IP を使用するとすぐにブロックされます。プロキシのローテーション — 異なる IP アドレス間の切り替え — はこの問題を解決します。Java では、これをいくつかの方法で実現できます。
オプション 1: ローテーションプロキシエンドポイントを介したローテーション
最も簡単なアプローチは、各リクエストごとに IP を自動的に変更する単一のエンドポイントを持つプロキシプロバイダーを使用することです。ホストとポートを 1 つ指定するだけで、ローテーションはプロバイダー側で行われます。ローテーションのためのコードを書く必要はありません。
オプション 2: プロキシのリストを介したアプリケーション側でのローテーション
@Component
public class ProxyRotator {
private final List<ProxyConfig> proxies;
private final AtomicInteger counter = new AtomicInteger(0);
public ProxyRotator(@Value("${proxy.list}") List<String> proxyList) {
this.proxies = proxyList.stream()
.map(this::parseProxy)
.collect(Collectors.toList());
}
public OkHttpClient getClientWithNextProxy() {
int index = counter.getAndIncrement() % proxies.size();
ProxyConfig config = proxies.get(index);
Proxy proxy = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress(config.getHost(), config.getPort())
);
return new OkHttpClient.Builder()
.proxy(proxy)
.build();
}
private ProxyConfig parseProxy(String proxyStr) {
// "host:port" または "user:pass@host:port" の形式の文字列を解析
String[] parts = proxyStr.split(":");
return new ProxyConfig(parts[0], Integer.parseInt(parts[1]));
}
}
オプション 3: ProxySelector を介したローテーション
Java は、ProxySelector という組み込みメカニズムを提供しており、宛先 URL に基づいて動的にプロキシを選択できます。これは、HttpURLConnection と Apache HttpClient で機能します:
ProxySelector.setDefault(new ProxySelector() {
private final List<Proxy> proxyPool = Arrays.asList(
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy1.example.com", 8080)),
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy2.example.com", 8080)),
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy3.example.com", 8080))
);
private final AtomicInteger idx = new AtomicInteger(0);
@Override
public List<Proxy> select(URI uri) {
// ドメインに基づく選択ロジックを追加できます
int i = idx.getAndIncrement() % proxyPool.size();
return Collections.singletonList(proxyPool.get(i));
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
// 利用できないプロキシの処理ロジック
log.warn("Proxy failed: {}", sa);
}
});
Java タスクに適したプロキシの種類
プロキシの種類の選択は、具体的なタスクに依存します。企業向け Java アプリケーションでは、通常、3 つの主要なタイプを考慮します:
| プロキシの種類 | 速度 | 匿名性 | 最適な用途 |
|---|---|---|---|
| データセンター | 高い | 中程度 | オープンデータの解析、API テスト、高負荷 |
| レジデンシャル | 中程度 | 高い | 保護されたサイトの解析、ジオテスト、ブロックの回避 |
| モバイル | 中程度 | 最大 | モバイル API との統合、厳しいアンチフロード保護を持つサービス |
タスクの種類に関する実用的な推奨事項:
- 企業ゲートウェイ (Squid、Zscaler): プロキシの種類は IT 部門が決定します。Apache HttpClient または JVM のシステムプロパティを介して認証付き HTTP プロキシを使用してください。
- 公開データの解析 (ニュース、見積もり、オープン API): データセンターのプロキシが速度とコストのバランスで最適な選択です。
- e コマースの解析 (Wildberries、Ozon、Amazon): ローテーションを伴うレジデンシャルプロキシ。これらのプラットフォームはデータセンターの IP を積極的にブロックします。
- ジオテスト (ローカリゼーションのチェック、地域ごとの価格): 国/都市を選択できるレジデンシャルプロキシ。
- モバイルサービスおよびアプリケーションとの統合: モバイルプロキシ — 実際のモバイルネットワークからの IP がリアルユーザーを最もよく模倣します。
Java におけるプロキシ設定の一般的なエラーとその修正方法
経験豊富な開発者でも、Java におけるプロキシ設定で同じ問題に直面することがあります。最も一般的なものを見ていきましょう。
1. プロキシが設定されているが、トラフィックが直接行われる
原因: JVM のシステムプロパティを設定しましたが、OkHttp や Reactor Netty (WebClient) を使用しているため、それらを無視しています。
解決策: 上記のセクションで示したように、特定の HTTP クライアントの設定でプロキシを明示的に設定してください。
2. HTTPS 経由のプロキシで javax.net.ssl.SSLHandshakeException
原因: 企業プロキシが SSL 検査 (MITM) を行い、証明書を置き換えます。Java は企業 CA を信頼しません。
解決策: 企業証明書を Java truststore にインポートします:
keytool -import -trustcacerts \ -alias corporate-ca \ -file corporate-ca.crt \ -keystore $JAVA_HOME/lib/security/cacerts \ -storepass changeit
3. java.net.ConnectException: 接続が拒否されました
原因: プロキシのホストまたはポートが無効; プロキシサーバーが利用できない; ファイアウォールが接続をブロックしています。
診断:
# プロキシの可用性を確認 curl -x http://proxy.example.com:8080 https://httpbin.org/ip # または telnet を使用 telnet proxy.example.com 8080
4. プロキシは HTTP では機能するが、HTTPS では機能しない
原因: http.proxyHost/Porthttps.proxyHost/Port が設定されていません。
解決策: 両方のプロパティセットを設定してください。HTTP プロキシを介した HTTPS には CONNECT トンネリングメソッドが使用されます — プロキシがそれをサポートしていることを確認してください。
5. 407 プロキシ認証が必要
原因: プロキシが認証を要求していますが、それが設定されていません。
解決策: JVM のシステムプロパティに Authenticator を追加するか、Apache HttpClient / proxyAuthenticator を設定します (上記の例を参照)。
6. プロキシのローテーション時のメモリリーク
原因: 各リクエストごとに新しい OkHttpClient インスタンスが作成されます。各クライアントは独自のスレッドプールと接続を保持します。
解決策: クライアントを再利用します。プロキシごとに複数のクライアント (1 つずつ) のプールを作成し、新しいものを作成するのではなく、それらをローテーションします。
7. 診断のためのデバッグログの有効化
# application.properties — プロキシ診断用のログを有効にします logging.level.org.apache.http=DEBUG logging.level.org.apache.http.wire=DEBUG logging.level.reactor.netty.http.client=DEBUG # JVM 用 — システムプロパティ -Djava.net.debug=all
結論
Java アプリケーションでのプロキシ設定は、内部で使用される HTTP クライアントを理解することが必要です。JVM のシステムプロパティは HttpURLConnection に対する迅速なスタートですが、OkHttp、WebClient、および Apache HttpClient には明示的な設定が必要です。企業アプリケーションでは、プロキシのパラメータを application.properties に移動し、異なる環境のために Spring プロファイルを使用することをお勧めします。プロキシのローテーションは、ローテーションエンドポイントを持つプロバイダーに委任するのが最も簡単で信頼性が高いです。
あなたの Java アプリケーションが保護されたプラットフォームからデータを解析したり、外部リクエスト時に高い匿名性を必要とする場合は、レジデンシャルプロキシを検討することをお勧めします — それらは実際の家庭ユーザーの IP を持っているため、アンチフロードシステムによるブロックの可能性が大幅に低下します。高負荷のオープンデータ解析には、データセンターのプロキシが最適です — それらはトラフィックコストが低く、高速を提供します。
```