تتصل تطبيقات جافا المؤسسية بانتظام بواجهات برمجة التطبيقات الخارجية، وتقوم بتحليل البيانات أو تعمل في شبكات مؤسسية معزولة - وفي كل من هذه الحالات، في وقت ما، يبرز سؤال توجيه الحركة عبر خادم بروكسي. يبدو إعداد البروكسي في جافا مهمة بسيطة، حتى تواجه أن RestTemplate وOkHttp وWebClient تتصرف بشكل مختلف، وأن خصائص النظام JVM لا تعمل في كل مكان. في هذا الدليل - أمثلة شيفرة محددة، وشرح للمعوقات، وتوصيات لاختيار نوع البروكسي لمهام مختلفة.
لماذا تحتاج تطبيقات جافا إلى بروكسي
تظل جافا واحدة من اللغات الرئيسية في تطوير المؤسسات. خدمات سبرينغ بوت، وهياكل الميكروسيرفيس، ومهام الدفعة، وخطوط ETL - كل ذلك يقوم بانتظام بإجراء طلبات HTTP إلى العالم الخارجي. وفي كل من هذه السيناريوهات، يحل البروكسي مشكلة معينة:
- الشبكات المؤسسية مع بوابة بروكسي إلزامية. في العديد من الشركات، تمر كل حركة المرور الصادرة عبر بروكسي مؤسسي (Squid، Zscaler، BlueCoat). بدون إعدادها، لن يتمكن التطبيق من الوصول إلى الإنترنت.
- تحليل وجمع البيانات. تستخدم الخدمات التي تجمع البيانات من المواقع الخارجية (الأسعار، الاقتباسات، الأخبار) البروكسي لتجاوز حدود المعدل والحظر حسب IP.
- اختبار الجغرافيا. تتحقق فرق ضمان الجودة من كيفية تصرف واجهة برمجة التطبيقات أو الموقع من مناطق مختلفة - يسمح البروكسي بمحاكاة الطلب من الدولة المطلوبة.
- تجاوز القيود على جانب واجهة برمجة التطبيقات المستهدفة. تحتوي بعض واجهات برمجة التطبيقات الخارجية على حدود لعدد الطلبات من IP واحد. يسمح تدوير البروكسي بتوزيع الحمل.
- الأمان وإخفاء الهوية. إخفاء IP الحقيقي للتطبيق عند الاتصال بالخدمات الخارجية - ممارسة قياسية للتكاملات الحساسة.
اعتمادًا على المهمة، تختلف الأساليب لإعداد البروكسي في جافا بشكل كبير. تعتبر خصائص النظام العالمية JVM الخيار الأسهل، لكنها لا تعمل مع بعض عملاء HTTP. تتطلب OkHttp وWebClient تكوينًا صريحًا. دعونا نستعرض كل حالة على حدة.
الإعداد العالمي من خلال خصائص النظام JVM
أسرع طريقة لتوجيه كل حركة مرور 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. من خلال ملف 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;
}
});
البروكسي في سبرينغ بوت: RestTemplate وApache HttpClient
RestTemplate هو عميل HTTP القياسي المتزامن في سبرينغ. بشكل افتراضي، يستخدم 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 تفاعلي في سبرينغ، مبني على 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 كـ بين بروتوتيب وأعد ضبط الإعدادات من خلال .mutate().
إعداد البروكسي في OkHttp
OkHttp هي مكتبة HTTP شهيرة تستخدمها العديد من تطبيقات جافا وأندرويد، بالإضافة إلى بعض تكاملات سبرينغ (مثل 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 مع سبرينغ بوت عبر بين:
@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 في جافا: الخصائص والأمثلة
SOCKS5 هو بروتوكول منخفض المستوى مقارنة ببروكسي HTTP. يعمل على مستوى النقل ويدعم أي بروتوكولات (HTTP، HTTPS، FTP، إلخ)، بالإضافة إلى حل DNS على جانب البروكسي. بالنسبة لتطبيقات جافا، يعتبر 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: بشكل افتراضي، تقوم جافا بحل 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.net.Proxy.Type.SOCKS) |
| الاستخدام | البوابات المؤسسية، تحليل الويب | إخفاء الهوية، بروتوكولات غير قياسية |
تدوير البروكسي في تطبيقات جافا
إذا كان التطبيق يقوم بعدد كبير من الطلبات إلى مورد واحد، فإن استخدام IP بروكسي واحد سيؤدي بسرعة إلى الحظر. يحل تدوير البروكسي - التبديل بين عناوين IP المختلفة - هذه المشكلة. في جافا، يتم تنفيذ ذلك بعدة طرق.
الخيار 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
توفر جافا آلية مدمجة 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);
}
});
أي نوع من البروكسي يجب اختياره لمهام جافا
يعتمد اختيار نوع البروكسي على المهمة المحددة. بالنسبة لتطبيقات جافا المؤسسية، يتم عادةً النظر في ثلاثة أنواع أساسية:
| نوع البروكسي | السرعة | إخفاء الهوية | أفضل ما يناسب |
|---|---|---|---|
| بروكسيات مراكز البيانات | عالية | متوسطة | تحليل البيانات المفتوحة، اختبار واجهات برمجة التطبيقات، تحميل عالي |
| بروكسيات سكنية | متوسطة | عالية | تحليل المواقع المحمية، اختبار الجغرافيا، تجاوز الحظر |
| بروكسيات موبايل | متوسطة | قصوى | التكامل مع واجهات برمجة التطبيقات المحمولة، الخدمات ذات الحماية الصارمة ضد الاحتيال |
توصيات عملية لأنواع المهام:
- بوابة مؤسسية (Squid، Zscaler): يحدد قسم تكنولوجيا المعلومات نوع البروكسي. استخدم بروكسي HTTP مع مصادقة عبر Apache HttpClient أو خصائص النظام JVM.
- تحليل البيانات العامة (الأخبار، الاقتباسات، واجهات برمجة التطبيقات المفتوحة): بروكسيات مراكز البيانات - الخيار الأمثل من حيث السرعة والتكلفة.
- تحليل التجارة الإلكترونية (Wildberries، Ozon، Amazon): بروكسيات سكنية مع تدوير. هذه المنصات تقوم بحظر IPs من مراكز البيانات بنشاط.
- اختبار الجغرافيا (التحقق من التوطين، الأسعار حسب المناطق): بروكسيات سكنية مع اختيار الدولة/المدينة.
- التكامل مع الخدمات والتطبيقات المحمولة: بروكسيات موبايل - IP من شبكات موبايل حقيقية تحاكي أفضل المستخدمين الحقيقيين.
الأخطاء الشائعة عند إعداد البروكسي في جافا وكيفية إصلاحها
حتى المطورين ذوي الخبرة يواجهون نفس المشكلات عند إعداد البروكسي في جافا. دعونا نستعرض الأكثر شيوعًا.
1. تم إعداد البروكسي، لكن الحركة لا تزال تمر مباشرة
السبب: لقد قمت بتعيين خصائص النظام JVM، لكنك تستخدم OkHttp أو Reactor Netty (WebClient) التي تتجاهلها.
الحل: قم بإعداد البروكسي بشكل صريح في تكوين عميل HTTP المحدد، كما هو موضح في الأقسام أعلاه.
2. javax.net.ssl.SSLHandshakeException عند HTTPS عبر البروكسي
السبب: يقوم البروكسي المؤسسي بإجراء فحص SSL (MITM) عن طريق استبدال الشهادة. لا تثق جافا في CA المؤسسي.
الحل: استورد الشهادة المؤسسية إلى truststore الخاص بجافا:
keytool -import -trustcacerts \ -alias corporate-ca \ -file corporate-ca.crt \ -keystore $JAVA_HOME/lib/security/cacerts \ -storepass changeit
3. java.net.ConnectException: Connection refused
الأسباب: مضيف أو منفذ البروكسي غير صحيح؛ خادم البروكسي غير متاح؛ جدار الحماية يمنع الاتصال.
التشخيص:
# تحقق من توفر البروكسي 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.
الحل: قم بتعيين كلا المجموعتين من الخصائص. يتم استخدام طريقة نفق CONNECT لـ HTTPS عبر بروكسي HTTP - تأكد من أن البروكسي يدعم ذلك.
5. 407 Proxy Authentication Required
السبب: يتطلب البروكسي مصادقة، لكنها غير معدة.
الحل: أضف 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 # لجافا - خاصية النظام -Djava.net.debug=all
الخاتمة
يتطلب إعداد البروكسي في تطبيقات جافا فهمًا لنوع عميل HTTP المستخدم في الخلفية. تعتبر خصائص النظام JVM نقطة انطلاق سريعة لـ HttpURLConnection، لكن OkHttp وWebClient وApache HttpClient تتطلب تكوينًا صريحًا. بالنسبة للتطبيقات المؤسسية، يُوصى بإخراج معلمات البروكسي إلى application.properties واستخدام ملفات تعريف سبرينغ لبيئات مختلفة. من الأفضل تفويض تدوير البروكسي إلى مزود مع نقطة نهاية دوارة - فهذا أسهل وأكثر موثوقية من تنفيذه بنفسك.
إذا كان تطبيق جافا الخاص بك يقوم بتحليل البيانات من منصات محمية أو يتطلب إخفاء هوية عالية عند إجراء طلبات خارجية، نوصي بالنظر في بروكسيات سكنية - لديها IP حقيقي لمستخدمين منزليين، مما يقلل بشكل كبير من احتمال الحظر من أنظمة مكافحة الاحتيال. بالنسبة لتحليل البيانات المفتوحة ذات الحمل العالي، تعتبر بروكسيات مراكز البيانات الخيار الأمثل - حيث توفر أقصى سرعة بتكلفة أقل لحركة المرور.
```