Как да използвам Socks 5 прокси с Apache HTTP Client 4?

Опитвам се да създам приложение, което изпраща HTTP заявки чрез Apache HC 4 през SOCKS5 прокси. Не мога да използвам глобален прокси на приложението, защото приложението е многонишково (имам нужда от различно прокси за всяко HttpClient копие). Не намерих примери за използване на SOCKS5 с HC4. Как мога да го използвам?


person user3511070    schedule 08.04.2014    source източник


Отговори (4)


SOCK е прокси протокол на ниво TCP/IP, а не HTTP. Не се поддържа от HttpClient веднага.

Човек може да персонализира HttpClient за установяване на връзки чрез SOCKS прокси чрез използване на персонализирана фабрика за гнезда за връзка

РЕДАКТИРАНЕ: се променя на SSL вместо обикновени сокети

Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory>create()
        .register("http", PlainConnectionSocketFactory.INSTANCE)
        .register("https", new MyConnectionSocketFactory(SSLContexts.createSystemDefault()))
        .build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);
CloseableHttpClient httpclient = HttpClients.custom()
        .setConnectionManager(cm)
        .build();
try {
    InetSocketAddress socksaddr = new InetSocketAddress("mysockshost", 1234);
    HttpClientContext context = HttpClientContext.create();
    context.setAttribute("socks.address", socksaddr);

    HttpHost target = new HttpHost("localhost", 80, "http");
    HttpGet request = new HttpGet("/");

    System.out.println("Executing request " + request + " to " + target + " via SOCKS proxy " + socksaddr);
    CloseableHttpResponse response = httpclient.execute(target, request, context);
    try {
        System.out.println("----------------------------------------");
        System.out.println(response.getStatusLine());
        EntityUtils.consume(response.getEntity());
    } finally {
        response.close();
    }
} finally {
    httpclient.close();
}

static class MyConnectionSocketFactory extends SSLConnectionSocketFactory {

    public MyConnectionSocketFactory(final SSLContext sslContext) {
        super(sslContext);
    }

    @Override
    public Socket createSocket(final HttpContext context) throws IOException {
        InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address");
        Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr);
        return new Socket(proxy);
    }

}
person ok2c    schedule 09.04.2014
comment
Добре благодаря. Опитах се да използвам този код, но целевият ми хост е https. Ако предам https като схема към конструктора на HttpHost, той казва, че схемата „https“ не се поддържа. Ако предам http, той казва, че отговорът е невалиден (защото е SSL, мисля) - person user3511070; 09.04.2014
comment
Няма голяма разлика, като се има предвид, че SOCKS е протокол на ниво TCP/IP. Вижте актуализирани кодови фрагменти - person ok2c; 09.04.2014
comment
Контекстната променлива не се използва, доколкото виждам? - person fragorl; 17.02.2015
comment
HttpClientContext context = HttpClientContext.create(); не е използван - person soulmachine; 03.09.2015
comment
Не изпълнява https заявка: HttpHost target = new HttpHost("localhost", 80, "http"); - person Robinho; 09.03.2016
comment
CloseableHttpResponse отговор = httpclient.execute(цел, заявка); трябва да бъде CloseableHttpResponse response = httpclient.execute(target, request, context); - person gaurav; 30.08.2016
comment
Директно копирайте и поставете вашия код и първоначално не работи за мен. тогава разбирам, че в моя случай трябва да регистрирам персонализиран SOCK базиран PlainConnectionSocketFactory на http и тогава работи. Публикувайте го тук, в случай че някой друг има същия проблем. - person qqibrow; 05.10.2016
comment
Възможно ли е да се използва socks5 прокси с удостоверяване? Опитах се да използвам CredentialsProvider, но без успех. Изглежда, че httpclient игнорира предоставените идентификационни данни и вместо това използва моето локално потребителско име. - person Emptyfruit; 10.07.2018
comment
това е неправилно: .register(http, PlainConnectionSocketFactory.INSTANCE). Трябва да регистрирате обикновен сокет, който заменя connectSocket, точно както за https. Вижте отговора от b10y - person Sergio; 30.03.2019

Отговорът по-горе работи доста добре, освен ако вашата страна също не отрови DNS записите. Много е трудно да се каже на Java „не използва моите DNS сървъри, докато се свързва чрез прокси“, както е адресирано в тези два въпроса:

java runtime 6 със socks v5 прокси - Възможно ли е?

Как да получа URL връзка чрез прокси в java?

Също така е трудно за Apache HttpClient, тъй като той също се опитва да разрешава имена на хостове локално. Чрез известна модификация на кода по-горе това може да се реши:

static class FakeDnsResolver implements DnsResolver {
    @Override
    public InetAddress[] resolve(String host) throws UnknownHostException {
        // Return some fake DNS record for every request, we won't be using it
        return new InetAddress[] { InetAddress.getByAddress(new byte[] { 1, 1, 1, 1 }) };
    }
}

static class MyConnectionSocketFactory extends PlainConnectionSocketFactory {
    @Override
    public Socket createSocket(final HttpContext context) throws IOException {
        InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address");
        Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr);
        return new Socket(proxy);
    }

    @Override
    public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress,
            InetSocketAddress localAddress, HttpContext context) throws IOException {
        // Convert address to unresolved
        InetSocketAddress unresolvedRemote = InetSocketAddress
                .createUnresolved(host.getHostName(), remoteAddress.getPort());
        return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context);
    }
}

static class MySSLConnectionSocketFactory extends SSLConnectionSocketFactory {

    public MySSLConnectionSocketFactory(final SSLContext sslContext) {
        // You may need this verifier if target site's certificate is not secure
        super(sslContext, ALLOW_ALL_HOSTNAME_VERIFIER);
    }

    @Override
    public Socket createSocket(final HttpContext context) throws IOException {
        InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address");
        Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr);
        return new Socket(proxy);
    }

    @Override
    public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress,
            InetSocketAddress localAddress, HttpContext context) throws IOException {
        // Convert address to unresolved
        InetSocketAddress unresolvedRemote = InetSocketAddress
                .createUnresolved(host.getHostName(), remoteAddress.getPort());
        return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context);
    }
}

public static void main(String[] args) throws Exception {
    Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory> create()
            .register("http", new MyConnectionSocketFactory())
            .register("https", new MySSLConnectionSocketFactory(SSLContexts.createSystemDefault())).build();
    PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg, new FakeDnsResolver());
    CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(cm).build();
    try {
        InetSocketAddress socksaddr = new InetSocketAddress("mysockshost", 1234);
        HttpClientContext context = HttpClientContext.create();
        context.setAttribute("socks.address", socksaddr);

        HttpGet request = new HttpGet("https://www.funnyordie.com");

        System.out.println("Executing request " + request + " via SOCKS proxy " + socksaddr);
        CloseableHttpResponse response = httpclient.execute(request, context);
        try {
            System.out.println("----------------------------------------");
            System.out.println(response.getStatusLine());
            int i = -1;
            InputStream stream = response.getEntity().getContent();
            while ((i = stream.read()) != -1) {
                System.out.print((char) i);
            }
            EntityUtils.consume(response.getEntity());
        } finally {
            response.close();
        }
    } finally {
        httpclient.close();
    }
}
person b10y    schedule 08.08.2014
comment
Това беше една от най-полезните информации за SO, които съм намирал. Всеки, който иска да използва HttpClient през SOCKS прокси и трябва да позволи на SOCKS проксито да разрешава имена на хостове, това е начинът да го направи. - person Stephen Roebuck; 18.09.2017
comment
@b10y Получавам грешка в кода. Публикувах нов въпрос stackoverflow.com/questions/56030901/. Можете ли да погледнете, моля? - person tarun14110; 08.05.2019

Вдъхновен от отговора на @oleg. Можете да направите помощна програма, която ви дава правилно конфигуриран CloseableHttpClient без специални ограничения за това как да го извикате.

Можете да използвате ProxySelector в ConnectionSocketFactory, за да изберете проксито.

Клас помощник за конструиране на екземпляри на CloseableHttpClient:

import org.apache.http.HttpHost;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
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.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.ssl.SSLContexts;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.*;

public final class HttpHelper {
    public static CloseableHttpClient createClient()
    {
        Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", ProxySelectorPlainConnectionSocketFactory.INSTANCE)
                .register("https", new ProxySelectorSSLConnectionSocketFactory(SSLContexts.createSystemDefault()))
                .build();
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);
        return HttpClients.custom()
                .setConnectionManager(cm)
                .build();
    }

    private enum ProxySelectorPlainConnectionSocketFactory implements ConnectionSocketFactory {
        INSTANCE;

        @Override
        public Socket createSocket(HttpContext context) {
            return HttpHelper.createSocket(context);
        }

        @Override
        public Socket connectSocket(int connectTimeout, Socket sock, HttpHost host, InetSocketAddress remoteAddress, InetSocketAddress localAddress, HttpContext context) throws IOException {
            return PlainConnectionSocketFactory.INSTANCE.connectSocket(connectTimeout, sock, host, remoteAddress, localAddress, context);
        }
    }

    private static final class ProxySelectorSSLConnectionSocketFactory extends SSLConnectionSocketFactory {
        ProxySelectorSSLConnectionSocketFactory(SSLContext sslContext) {
            super(sslContext);
        }

        @Override
        public Socket createSocket(HttpContext context) {
            return HttpHelper.createSocket(context);
        }
    }

    private static Socket createSocket(HttpContext context) {
        HttpHost httpTargetHost = (HttpHost) context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST);
        URI uri = URI.create(httpTargetHost.toURI());
        Proxy proxy = ProxySelector.getDefault().select(uri).iterator().next();
        return new Socket(proxy);
    }
}

Клиентски код, използващ това:

import com.okta.tools.helpers.HttpHelper;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.URI;

public class Main {
    public static void main(String[] args) throws IOException {
        URI uri = URI.create("http://example.com/");
        HttpGet request = new HttpGet(uri);
        try (CloseableHttpClient closeableHttpClient = HttpHelper.createClient()) {
            try (CloseableHttpResponse response = closeableHttpClient.execute(request)) {
                System.out.println("----------------------------------------");
                System.out.println(response.getStatusLine());
                System.out.println(EntityUtils.toString(response.getEntity()));
            }
        }
    }
}
person Alain O'Dea    schedule 01.10.2018

Ако знаете кои URI адреси трябва да отидат до прокси, можете също да използвате ProxySelector на нисък слой: https://docs.oracle.com/javase/7/docs/technotes/guides/net/proxies.html където за всяка осъществена връзка на Socket можете да решите какво трябва да се използват проксита.

Изглежда нещо подобно:

public class MyProxySelector extends ProxySelector {
        ...

        public java.util.List<Proxy> select(URI uri) {
        ...
          if (uri is what I need) {
             return list of my Proxies
          }
        ...
        }
        ...
}
 

След това използвате вашия селектор:

public static void main(String[] args) {
        MyProxySelector ps = new MyProxySelector(ProxySelector.getDefault());
        ProxySelector.setDefault(ps);
        // rest of the application
}
person Pavol Zibrita    schedule 02.11.2020