برنامههای Java شرکتی به طور منظم به APIهای خارجی مراجعه میکنند، دادهها را تجزیه میکنند یا در شبکههای شرکتی ایزوله کار میکنند — و در هر یک از این موارد، دیر یا زود سوالی درباره مسیریابی ترافیک از طریق سرور پروکسی پیش میآید. تنظیم پروکسی در Java به نظر کار سادهای میرسد، تا زمانی که با این واقعیت مواجه شوید که RestTemplate، OkHttp و WebClient رفتارهای متفاوتی دارند و ویژگیهای سیستم JVM در همه جا کار نمیکنند. در این راهنما — مثالهای کد مشخص، توضیحاتی درباره مشکلات و توصیههایی برای انتخاب نوع پروکسی برای وظایف مختلف ارائه شده است.
چرا برنامههای Java به پروکسی نیاز دارند
Java یکی از زبانهای اصلی توسعه شرکتی باقی مانده است. خدمات Spring Boot، معماریهای میکروسرویس، وظایف دستهای، خطوط لوله ETL — همه اینها به طور منظم درخواستهای HTTP به دنیای خارجی ارسال میکنند. و در هر یک از این سناریوها، پروکسی یک وظیفه خاص را حل میکند:
- شبکههای شرکتی با پروکسی گیتوی الزامی. در بسیاری از شرکتها، تمام ترافیک خروجی از طریق پروکسی شرکتی (Squid، Zscaler، BlueCoat) عبور میکند. بدون تنظیم آن، برنامه به سادگی نمیتواند به اینترنت دسترسی پیدا کند.
- تجزیه و جمعآوری دادهها. خدماتی که دادهها را از وبسایتهای خارجی جمعآوری میکنند (قیمتها، نقل قولها، اخبار)، از پروکسی برای دور زدن محدودیتهای نرخ و مسدودیتهای IP استفاده میکنند.
- آزمایش جغرافیایی. تیمهای QA بررسی میکنند که API یا وبسایت چگونه از مناطق مختلف عمل میکند — پروکسی این امکان را فراهم میکند که درخواستهایی از کشور مورد نظر شبیهسازی شود.
- دور زدن محدودیتها در سمت API هدف. برخی از APIهای خارجی محدودیتهایی برای تعداد درخواستها از یک IP دارند. چرخش پروکسی این امکان را فراهم میکند که بار را توزیع کنیم.
- امنیت و ناشناسسازی. پنهان کردن IP واقعی برنامه هنگام مراجعه به خدمات خارجی — یک عمل استاندارد برای ادغامهای حساس است.
بسته به وظیفه، رویکردهای تنظیم پروکسی در Java به طور قابل توجهی متفاوت است. ویژگیهای جهانی سیستم JVM — سادهترین گزینه است، اما با برخی از کلاینتهای HTTP کار نمیکند. OkHttp و WebClient به پیکربندی صریح نیاز دارند. هر مورد را به ترتیب بررسی خواهیم کرد.
تنظیمات جهانی از طریق ویژگیهای سیستم JVM
سریعترین راه برای هدایت تمام ترافیک HTTP برنامه Java از طریق پروکسی — استفاده از ویژگیهای سیستم 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. از طریق فایل application.properties در Spring Boot (با اعمال بعدی از طریق @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 — کلاینت HTTP همزمان استاندارد در Spring است. به طور پیشفرض، از SimpleClientHttpRequestFactory استفاده میکند که به HttpURLConnection تکیه دارد و ویژگیهای سیستم JVM را میخواند. اما برای تنظیمات انعطافپذیرتر (زمانهای انتظار، استخر اتصالات، پروکسی با احراز هویت) توصیه میشود از Apache HttpClient به عنوان بکاند استفاده کنید.
وابستگی در Maven:
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3.1</version>
</dependency>
پیکربندی RestTemplate با پروکسی از طریق Apache HttpClient 5:
@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 یا متغیرهای محیطی قرار داده شوند که برای محیطهای تولید/آزمایشی/توسعه با پروکسیهای مختلف حیاتی است.
اگر نیاز دارید که بخشی از درخواستها از طریق پروکسی و بخشی دیگر به طور مستقیم انجام شود، دو بین جداگانه RestTemplate با نامهای مختلف ایجاد کنید و از @Qualifier برای تزریق مورد نیاز استفاده کنید.
پروکسی در Spring WebClient (پشته واکنشی)
WebClient — کلاینت HTTP واکنشی Spring است که بر پایه Reactor Netty ساخته شده است. این کلاینت ویژگیهای سیستم JVM را برای پروکسی نمیخواند — تنظیمات از طریق ProxyProvider هنگام ایجاد کلاینت انجام میشود.
وابستگی (در spring-boot-starter-webflux گنجانده شده است):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
پیکربندی WebClient با پروکسی HTTP:
@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();
}
}
برای پروکسی SOCKS5 در WebClient:
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 به عنوان یک بین prototype استفاده کنید و تنظیمات را از طریق .mutate() بازنویسی کنید.
تنظیم پروکسی در OkHttp
OkHttp — یک کتابخانه HTTP محبوب است که توسط بسیاری از برنامههای Java و Android و همچنین برخی از ادغامهای Spring (به عنوان مثال، Feign با بکاند OkHttp) استفاده میشود. این کتابخانه مکانیزم تنظیم پروکسی خاص خود را دارد و همچنین ویژگیهای سیستم JVM را نمیخواند.
تنظیمات پایه پروکسی HTTP/HTTPS در OkHttp:
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());
}
ادغام OkHttp با Spring Boot از طریق بین:
@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 به همراه پروکسیهای مسکونی برای تجزیه دادهها استفاده میکنید، مهم است که استخر اتصالات را به درستی تنظیم کنید. پروکسیهای مسکونی معمولاً تأخیر بیشتری نسبت به پروکسیهای مرکز داده دارند، بنابراین زمانهای انتظار را افزایش دهید و اندازه استخر را کاهش دهید تا از خالی شدن اتصالات موجود جلوگیری کنید.
پروکسی SOCKS5 در Java: ویژگیها و مثالها
SOCKS5 — پروتکل سطح پایینتری نسبت به پروکسی HTTP است. این پروتکل در سطح حمل و نقل کار میکند و از هر پروتکلی (HTTP، HTTPS، FTP و غیره) پشتیبانی میکند و همچنین حل DNS را در سمت پروکسی پشتیبانی میکند. برای برنامههای Java، SOCKS5 به ویژه زمانی مفید است که نیاز به پروکسیگذاری نه تنها HTTP، بلکه سایر اتصالات TCP نیز باشد.
SOCKS5 از طریق ویژگیهای سیستم JVM:
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");
SOCKS5 در OkHttp (از طریق 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 استفاده کنید:
// DNS سفارشی که حل را از طریق SOCKS5 اجباری میکند
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 را در هر درخواست یا از طریق یک بازه زمانی مشخص تغییر میدهد. شما یک هاست و پورت مشخص میکنید و چرخش در سمت ارائهدهنده انجام میشود. نیازی به نوشتن کد برای چرخش نیست.
گزینه 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 شرکتی معمولاً سه نوع اصلی در نظر گرفته میشود:
| نوع پروکسی | سرعت | ناشناسسازی | بهترین برای |
|---|---|---|---|
| پروکسیهای مرکز داده | بالا | متوسط | تجزیه دادههای عمومی، آزمایش API، بار بالا |
| پروکسیهای مسکونی | متوسط | بالا | تجزیه وبسایتهای محافظتشده، آزمایش جغرافیایی، دور زدن مسدودیتها |
| پروکسیهای موبایل | متوسط | حداکثر | ادغام با APIهای موبایل، خدمات با محافظت ضد تقلب شدید |
توصیههای عملی برای انواع وظایف:
- گیتوی شرکتی (Squid، Zscaler): نوع پروکسی توسط بخش IT تعیین میشود. از پروکسی HTTP با احراز هویت از طریق Apache HttpClient یا ویژگیهای سیستم JVM استفاده کنید.
- تجزیه دادههای عمومی (اخبار، نقل قولها، APIهای عمومی): پروکسیهای مرکز داده — انتخاب بهینه از نظر سرعت و هزینه.
- تجزیه e-commerce (Wildberries، Ozon، Amazon): پروکسیهای مسکونی با چرخش. این پلتفرمها به شدت IPهای مرکز داده را مسدود میکنند.
- آزمایش جغرافیایی (بررسی محلیسازی، قیمتها در مناطق): پروکسیهای مسکونی با انتخاب کشور/شهر.
- ادغام با خدمات و برنامههای موبایل: پروکسیهای موبایل — IPهای واقعی از شبکههای موبایل به بهترین شکل کاربران واقعی را شبیهسازی میکنند.
خطاهای رایج در تنظیم پروکسی در Java و نحوه رفع آنها
حتی توسعهدهندگان با تجربه نیز با مشکلات مشابهی در تنظیم پروکسی در Java مواجه میشوند. بیایید رایجترین آنها را بررسی کنیم.
1. پروکسی تنظیم شده است، اما ترافیک به طور مستقیم میرود
دلیل: شما ویژگیهای سیستم JVM را تنظیم کردهاید، اما از OkHttp یا Reactor Netty (WebClient) استفاده میکنید که آنها را نادیده میگیرند.
راهحل: پروکسی را به طور صریح در پیکربندی کلاینت HTTP خاص تنظیم کنید، همانطور که در بخشهای بالا نشان داده شده است.
2. javax.net.ssl.SSLHandshakeException هنگام HTTPS از طریق پروکسی
دلیل: پروکسی شرکتی SSL-inspection (MITM) انجام میدهد و گواهی را جایگزین میکند. Java به CA شرکتی اعتماد ندارد.
راهحل: گواهی شرکتی را به truststore Java وارد کنید:
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/Port تنظیم شدهاند، اما نه https.proxyHost/Port.
راهحل: هر دو مجموعه ویژگی را تنظیم کنید. برای HTTPS از طریق پروکسی HTTP از روش تونلسازی CONNECT استفاده میشود — اطمینان حاصل کنید که پروکسی آن را پشتیبانی میکند.
5. 407 نیاز به احراز هویت پروکسی
دلیل: پروکسی نیاز به احراز هویت دارد، اما تنظیم نشده است.
راهحل: Authenticator را برای ویژگیهای سیستم JVM اضافه کنید یا CredentialsProvider را برای Apache HttpClient / proxyAuthenticator را برای OkHttp تنظیم کنید (مثالها در بالا).
6. نشت حافظه هنگام چرخش پروکسی
دلیل: در هر درخواست یک نمونه جدید از OkHttpClient ایجاد میشود. هر کلاینت استخر خود را از اتصالات و رشتهها نگه میدارد.
راهحل: کلاینتها را دوباره استفاده کنید. یک استخر از چندین کلاینت (به ازای هر پروکسی) ایجاد کنید و آنها را بچرخانید، نه اینکه جدید ایجاد کنید.
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های واقعی کاربران خانگی هستند که احتمال مسدود شدن را به طور قابل توجهی کاهش میدهد. برای تجزیه دادههای عمومی با بار بالا، پروکسیهای مرکز داده بهینهترین گزینه هستند — آنها حداکثر سرعت را با هزینه کمتر ترافیک فراهم میکنند.
```