دليل شامل لاستخدام بروكسيات HTTP في Java: HttpClient و OkHttp و Jsoup

تعلم كيفية إعداد بروكسيات HTTP في Java باستخدام HttpClient 11+ و OkHttp و Jsoup مع المصادقة وتجميع الاتصالات والمعالجة المتوازية.

دليل شامل لاستخدام بروكسيات HTTP في Java: HttpClient و OkHttp و Jsoup

إذا كنت تعمل على تطبيقات Java تحتاج إلى جمع بيانات من الويب أو إجراء اختبارات آلية أو الوصول إلى APIs مقيدة جغرافياً، فإن إتقان استخدام بروكسيات HTTP يعد مهارة أساسية. في هذا الدليل، سنغطي المكتبات الرئيسية في نظام Java البيئي مع أمثلة عملية قابلة للتشغيل.

لماذا تحتاج إلى بروكسيات HTTP في Java؟

البروكسيات تسمح لتطبيقك بـ:

  • تجاوز القيود الجغرافية على المحتوى
  • توزيع الطلبات عبر عناوين IP متعددة لتجنب الحظر
  • إجراء اختبارات من مواقع جغرافية مختلفة
  • جمع البيانات بشكل موثوق دون تعريض عنوان IP الأصلي للحظر

Java 11+ HttpClient مع ProxySelector و Authenticator

منذ Java 11، أصبح HttpClient جزءاً من JDK القياسي. إليك كيفية إعداده مع بروكسي مصادَق:

import java.net.URI;
import java.net.InetSocketAddress;
import java.net.ProxySelector;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class JavaHttpClientProxyExample {
    
    private static final String PROXY_HOST = "gate.proxyhat.com";
    private static final int PROXY_PORT = 8080;
    private static final String PROXY_USER = "user-country-US";
    private static final String PROXY_PASS = "your_password";

    public static void main(String[] args) throws Exception {
        // إنشاء ProxySelector للبروكسي
        ProxySelector proxySelector = ProxySelector.of(
            new InetSocketAddress(PROXY_HOST, PROXY_PORT)
        );

        // إعداد Authenticator للمصادقة
        Authenticator authenticator = new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(PROXY_USER, PROXY_PASS.toCharArray());
            }
        };

        // بناء HttpClient مع البروكسي
        HttpClient client = HttpClient.newBuilder()
            .proxy(proxySelector)
            .authenticator(authenticator)
            .connectTimeout(Duration.ofSeconds(30))
            .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());
        
        client.close();
    }
}

إعادة المحاولة مع HttpClient

للحصول على موثوقية أفضل، يمكنك تنفيذ سياسة إعادة المحاولة:

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;

public class HttpClientRetryExample {
    
    private static final int MAX_RETRIES = 3;
    private static final long RETRY_DELAY_MS = 1000;

    public static HttpResponse<String> sendWithRetry(
            HttpClient client,
            HttpRequest request,
            int maxRetries
    ) throws Exception {
        
        AtomicInteger attempt = new AtomicInteger(0);
        Exception lastException = null;
        
        while (attempt.get() < maxRetries) {
            try {
                HttpResponse<String> response = client.send(
                    request,
                    HttpResponse.BodyHandlers.ofString()
                );
                
                // إعادة المحاولة على أخطاء الخادم
                if (response.statusCode() >= 500 && response.statusCode() < 600) {
                    throw new RuntimeException("Server error: " + response.statusCode());
                }
                
                return response;
                
            } catch (Exception e) {
                lastException = e;
                attempt.incrementAndGet();
                System.err.println("Attempt " + attempt + " failed: " + e.getMessage());
                
                if (attempt.get() < maxRetries) {
                    Thread.sleep(RETRY_DELAY_MS * attempt.get()); // exponential backoff
                }
            }
        }
        
        throw new RuntimeException("All retries exhausted", lastException);
    }
}

OkHttp مع Proxy و Authenticator

OkHttp هي مكتبة شائعة من Square توفر واجهة برمجة أنظف وأداءً ممتازاً:

import okhttp3.Authenticator;
import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;
import java.net.InetSocketAddress;
import java.net.Proxy;
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 PROXY_USER = "user-country-DE-city-berlin";
    private static final String PROXY_PASS = "your_password";

    public static void main(String[] args) throws Exception {
        // إنشاء البروكسي
        Proxy proxy = new Proxy(
            Proxy.Type.HTTP,
            new InetSocketAddress(PROXY_HOST, PROXY_PORT)
        );

        // إنشاء مصادق للبروكسي
        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(PROXY_USER, PROXY_PASS);
                return response.request().newBuilder()
                    .header("Proxy-Authorization", credential)
                    .build();
            }
        };

        // بناء OkHttpClient
        OkHttpClient client = new OkHttpClient.Builder()
            .proxy(proxy)
            .proxyAuthenticator(proxyAuthenticator)
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .retryOnConnectionFailure(true)
            .connectionPool(new okhttp3.ConnectionPool(10, 5, TimeUnit.MINUTES))
            .build();

        // إنشاء الطلب
        Request request = new Request.Builder()
            .url("https://httpbin.org/ip")
            .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
            .build();

        // إرسال الطلب
        try (Response response = client.newCall(request).execute()) {
            System.out.println("Status: " + response.code());
            System.out.println("Body: " + response.body().string());
        }
        
        client.dispatcher().executorService().shutdown();
        client.connectionPool().evictAll();
    }
}

Connection Pooling في OkHttp

OkHttp يدير تجمع اتصالات تلقائياً، لكن يمكنك تخصيصه:

// تجمع اتصالات مخصص: 20 اتصال، يبقى حياً لمدة 10 دقائق
ConnectionPool connectionPool = new ConnectionPool(20, 10, TimeUnit.MINUTES);

OkHttpClient client = new OkHttpClient.Builder()
    .connectionPool(connectionPool)
    .build();

// للحصول على إحصائيات التجمع
System.out.println("Active connections: " + client.connectionPool().connectionCount());
System.out.println("Idle connections: " + client.connectionPool().idleConnectionCount());

Jsoup مع دعم البروكسي لتحليل HTML

Jsoup هي المكتبة المفضلة لتحليل HTML في Java. إليك كيفية استخدامها مع بروكسي:

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class JsoupProxyExample {
    
    private static final String PROXY_HOST = "gate.proxyhat.com";
    private static final int PROXY_PORT = 8080;
    private static final String PROXY_USER = "user-country-JP";
    private static final String PROXY_PASS = "your_password";

    public static void main(String[] args) throws Exception {
        // الطريقة 1: استخدام Jsoup مباشرة مع بروكسي
        Document doc = Jsoup.connect("https://example.com")
            .proxy(PROXY_HOST, PROXY_PORT)
            .header("Proxy-Authorization", 
                java.util.Base64.getEncoder().encodeToString(
                    (PROXY_USER + ":" + PROXY_PASS).getBytes()
                ))
            .userAgent("Mozilla/5.0")
            .timeout(30000)
            .followRedirects(true)
            .get();
        
        System.out.println("Title: " + doc.title());
        
        // الطريقة 2: استخدام HttpClient ثم تحويل HTML إلى Jsoup
        Document docFromHttpClient = fetchWithHttpClient("https://example.com");
        Elements links = docFromHttpClient.select("a[href]");
        
        for (Element link : links) {
            System.out.println("Link: " + link.attr("href") + " - " + link.text());
        }
    }
    
    private static Document fetchWithHttpClient(String url) throws Exception {
        // إعداد HttpClient مع البروكسي
        System.setProperty("jdk.http.auth.proxying.disabledSchemes", "");
        
        Authenticator.setDefault(new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                if (getRequestorType() == RequestorType.PROXY) {
                    return new PasswordAuthentication(PROXY_USER, PROXY_PASS.toCharArray());
                }
                return null;
            }
        });
        
        HttpClient client = HttpClient.newBuilder()
            .proxy(ProxySelector.of(
                new java.net.InetSocketAddress(PROXY_HOST, PROXY_PORT)
            ))
            .build();
        
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .GET()
            .build();
        
        HttpResponse<String> response = client.send(
            request,
            HttpResponse.BodyHandlers.ofString()
        );
        
        // تحويل HTML إلى Document باستخدام Jsoup
        return Jsoup.parse(response.body(), url);
    }
}

Apache HttpClient (للأنظمة القديمة)

لا تزال العديد من الأنظمة تستخدم Apache HttpClient 4.x. إليك إعداد سريع:

import org.apache.http.HttpHost;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;

public class ApacheHttpClientProxyExample {
    
    private static final String PROXY_HOST = "gate.proxyhat.com";
    private static final int PROXY_PORT = 8080;
    private static final String PROXY_USER = "user-country-GB";
    private static final String PROXY_PASS = "your_password";

    public static void main(String[] args) throws Exception {
        // إعداد مدير الاتصالات مع تجمع
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(50);
        cm.setDefaultMaxPerRoute(20);

        // إعداد بيانات المصادقة للبروكسي
        CredentialsProvider credsProvider = new org.apache.http.impl.client.BasicCredentialsProvider();
        credsProvider.setCredentials(
            new AuthScope(PROXY_HOST, PROXY_PORT),
            new UsernamePasswordCredentials(PROXY_USER, PROXY_PASS)
        );

        // إنشاء HttpHost للبروكسي
        HttpHost proxy = new HttpHost(PROXY_HOST, PROXY_PORT);

        // بناء العميل
        try (CloseableHttpClient client = HttpClients.custom()
            .setConnectionManager(cm)
            .setProxy(proxy)
            .setDefaultCredentialsProvider(credsProvider)
            .build()) {
            
            HttpGet request = new HttpGet("https://httpbin.org/ip");
            
            try (org.apache.http.HttpResponse response = client.execute(request)) {
                String body = org.apache.http.util.EntityUtils.toString(response.getEntity());
                System.out.println("Response: " + body);
            }
        }
    }
}

المعالجة المتوازية مع Executors عبر مجموعة بروكسيات سكنية

لجمع البيانات بشكل فعال، تحتاج إلى توزيع الطلبات عبر عناوين IP متعددة بالتوازي:

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;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class ParallelScrapingExample {
    
    private static final String PROXY_HOST = "gate.proxyhat.com";
    private static final int PROXY_PORT = 8080;
    private static final String PROXY_USER = "user-country-US";
    private static final String PROXY_PASS = "your_password";
    private static final int THREAD_POOL_SIZE = 10;
    private static final int REQUEST_TIMEOUT_SECONDS = 30;

    // قائمة URLs المستهدفة
    private static final List<String> TARGET_URLS = List.of(
        "https://httpbin.org/ip",
        "https://httpbin.org/headers",
        "https://httpbin.org/user-agent",
        "https://example.com/page1",
        "https://example.com/page2"
    );

    public static void main(String[] args) throws Exception {
        // إنشاء تجمع الخيوط
        ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
        
        // إنشاء HttpClient مشترك
        HttpClient sharedClient = HttpClient.newBuilder()
            .proxy(ProxySelector.of(new InetSocketAddress(PROXY_HOST, PROXY_PORT)))
            .authenticator(new java.net.Authenticator() {
                @Override
                protected java.net.PasswordAuthentication getPasswordAuthentication() {
                    return new java.net.PasswordAuthentication(PROXY_USER, PROXY_PASS.toCharArray());
                }
            })
            .connectTimeout(Duration.ofSeconds(REQUEST_TIMEOUT_SECONDS))
            .executor(executor)
            .build();

        // تتبع التقدم
        AtomicInteger completed = new AtomicInteger(0);
        AtomicInteger failed = new AtomicInteger(0);

        // إنشاء مهام لكل URL مع جلسة بروكسي فريدة
        List<CompletableFuture<Void>> futures = TARGET_URLS.stream()
            .map(url -> CompletableFuture.runAsync(() -> {
                // استخدام جلسة فريدة لكل طلب (IP مختلف)
                String sessionUser = PROXY_USER + "-session-" + java.util.UUID.randomUUID().toString().substring(0, 8);
                
                // إعادة إنشاء العميل مع جلسة فريدة
                HttpClient clientWithSession = HttpClient.newBuilder()
                    .proxy(ProxySelector.of(new InetSocketAddress(PROXY_HOST, PROXY_PORT)))
                    .authenticator(new java.net.Authenticator() {
                        @Override
                        protected java.net.PasswordAuthentication getPasswordAuthentication() {
                            return new java.net.PasswordAuthentication(sessionUser, PROXY_PASS.toCharArray());
                        }
                    })
                    .connectTimeout(Duration.ofSeconds(REQUEST_TIMEOUT_SECONDS))
                    .build();
                
                try {
                    HttpRequest request = HttpRequest.newBuilder()
                        .uri(URI.create(url))
                        .timeout(Duration.ofSeconds(REQUEST_TIMEOUT_SECONDS))
                        .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
                        .GET()
                        .build();
                    
                    HttpResponse<String> response = clientWithSession.send(
                        request,
                        HttpResponse.BodyHandlers.ofString()
                    );
                    
                    System.out.printf("[%s] %s -> Status: %d%n", 
                        Thread.currentThread().getName(),
                        url,
                        response.statusCode()
                    );
                    
                    completed.incrementAndGet();
                    
                } catch (Exception e) {
                    System.err.printf("[%s] Failed: %s - Error: %s%n",
                        Thread.currentThread().getName(),
                        url,
                        e.getMessage()
                    );
                    failed.incrementAndGet();
                }
            }, executor))
            .toList();

        // انتظار انتهاء جميع المهام
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

        System.out.println("\n=== Summary ===");
        System.out.println("Completed: " + completed.get());
        System.out.println("Failed: " + failed.get());
        
        executor.shutdown();
        executor.awaitTermination(60, TimeUnit.SECONDS);
    }
}

ملاحظات TLS و SSLContext المخصص

عند التعامل مع شهادات SSL غير قياسية أو بيئات اختبار، قد تحتاج إلى تخصيص SSLContext:

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.SSLParameters;
import java.net.http.HttpClient;
import java.security.cert.X509Certificate;
import java.security.SecureRandom;

public class TlsConfigurationExample {

    /**
     * إنشاء SSLContext مع TrustManager مخصص (للبيئات غير الإنتاجية)
     * تحذير: لا تستخدم هذا في الإنتاج!
     */
    public static SSLContext createTrustAllSslContext() throws Exception {
        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 sslContext;
    }

    /**
     * إنشاء HttpClient مع تكوين TLS مخصص
     */
    public static HttpClient createClientWithCustomTls() throws Exception {
        SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
        sslContext.init(null, null, new SecureRandom());
        
        SSLParameters sslParams = new SSLParameters();
        sslParams.setProtocols(new String[]{"TLSv1.2", "TLSv1.3"});
        sslParams.setCipherSuites(new String[]{
            "TLS_AES_256_GCM_SHA384",
            "TLS_CHACHA20_POLY1305_SHA256",
            "TLS_AES_128_GCM_SHA256"
        });
        
        return HttpClient.newBuilder()
            .sslContext(sslContext)
            .sslParameters(sslParams)
            .build();
    }

    /**
     * للإنتاج: استخدام KeyStore مخصص مع شهادات موثوقة
     */
    public static SSLContext createKeyStoreSslContext(String keystorePath, String password) 
            throws Exception {
        java.security.KeyStore keyStore = java.security.KeyStore.getInstance("PKCS12");
        try (java.io.FileInputStream fis = new java.io.FileInputStream(keystorePath)) {
            keyStore.load(fis, password.toCharArray());
        }
        
        javax.net.ssl.KeyManagerFactory kmf = javax.net.ssl.KeyManagerFactory.getInstance(
            javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm()
        );
        kmf.init(keyStore, password.toCharArray());
        
        javax.net.ssl.TrustManagerFactory tmf = javax.net.ssl.TrustManagerFactory.getInstance(
            javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm()
        );
        tmf.init(keyStore);
        
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
        return sslContext;
    }
}

أفضل ممارسات TLS في الإنتاج

  • لا تعطل التحقق من SSL في بيئات الإنتاج
  • استخدم TLS 1.2 أو أحدث كحد أدنى
  • حدد Cipher Suites آمنة فقط
  • جدّد شهادات CA بانتظام
  • استخدم OCSP Stapling عند الإمكان

جدول مقارنة المكتبات

الميزة Java 11+ HttpClient OkHttp Apache HttpClient Jsoup
جزء من JDK نعم لا لا لا
دعم HTTP/2 نعم نعم جزئي لا (يعتمد على العميل)
WebSocket نعم نعم لا لا
Connection Pooling تلقائي متقدم متقدم لا
تحليل HTML لا لا لا نعم (ممتاز)
Reactive Streams نعم لا لا لا
صعوبة الإعداد متوسطة سهلة معقدة سهلة جداً

النقاط الرئيسية

  • Java 11+ HttpClient هو الخيار الأمثل للمشاريع الجديدة لكونه جزءاً من JDK
  • OkHttp يوفر واجهة برمجة أنظف وتجمع اتصالات متقدم
  • Jsoup مثالي لتحليل HTML مع دعم البروكسي المباشر
  • استخدم جلسات فريدة لكل طلب لتوزيع الطلبات على IPs مختلفة
  • طبّق سياسة إعادة المحاولة مع exponential backoff
  • لا تعطل التحقق من SSL في الإنتاج أبداً
  • استخدم Connection Pooling لتحسين الأداء

لمزيد من المعلومات حول بروكسيات ProxyHat وإمكانيات الاستهداف الجغرافي، راجع حالة استخدام جمع بيانات الويب أو صفحة الأسعار للاطلاع على الخطط المتاحة.

¿Listo para empezar?

Accede a más de 50M de IPs residenciales en más de 148 países con filtrado impulsado por IA.

Ver preciosProxies residenciales
← Volver al Blog