【问题标题】:How to use Socks 5 proxy with Apache HTTP Client 4?如何在 Apache HTTP Client 4 中使用 Socks 5 代理?
【发布时间】:2014-05-21 04:55:21
【问题描述】:

我正在尝试创建通过 SOCKS5 代理发送HTTP 请求的应用程序通过 Apache HC 4。 我不能使用 app-global 代理,因为 app 是多线程的(我需要为每个 HttpClient 实例使用不同的代理)。我没有发现 HC4 使用 SOCKS5 的示例。我该如何使用它?

【问题讨论】:

    标签: java dns apache-httpclient-4.x socks


    【解决方案1】:

    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);
        }
    
    }
    

    【讨论】:

    • 好的,谢谢。我尝试使用此代码,但我的目标主机是 https。如果我将“https”作为方案传递给 HttpHost 构造函数,则表示不支持方案“https”。如果我通过“http”,则表示响应无效(我认为是 SSL)
    • 没有太大区别,因为 SOCKS 是 TCP/IP 级别的协议。查看更新的代码 sn-ps
    • HttpClientContext context = HttpClientContext.create(); 未使用
    • 直接复制粘贴你的代码,一开始对我不起作用。然后我意识到在我的情况下,我必须在http 上注册一个基于PlainConnectionSocketFactory 的定制SOCK,然后它才能工作。把它贴在这里,以防其他人有同样的问题。
    • 是否可以通过身份验证来使用 socks5 代理?我尝试使用CredentialsProvider,但没有成功。似乎 httpclient 会忽略提供的凭据并改用我的本地用户名。
    【解决方案2】:

    上面的答案非常有效,除非您所在的国家/地区也破坏了 DNS 记录。很难说 Java“在通过代理连接时不要使用我的 DNS 服务器”,如以下两个问题所述:

    java runtime 6 with socks v5 proxy - Possible?

    How to get URL connection using proxy in 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();
        }
    }
    

    【讨论】:

    • 这是我找到的关于 SO 的最有用的信息之一。任何想通过 SOCKS 代理使用 HttpClient 并需要让 SOCKS 代理解析主机名的人,都可以这样做。
    • @b10y 我在代码中遇到错误。我发布了一个新问题stackoverflow.com/questions/56030901/…。你能看看吗?
    【解决方案3】:

    受@oleg 回答的启发。您可以创建一个实用程序,为您提供正确配置的 CloseableHttpClient,而对您的调用方式没有特殊限制。

    您可以使用 ConnectionSocketFactory 中的 ProxySelector 来选择代理。

    用于构造 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()));
                }
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      如果你知道哪些 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
      }
      

      【讨论】:

        猜你喜欢
        • 2011-01-20
        • 2023-04-03
        • 2023-01-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-01
        相关资源
        最近更新 更多