I'm trying to create app that sends HTTP
requests via Apache HC 4 via SOCKS5 proxy.
I can not use app-global proxy, because app is multi-threaded (I need different proxy for each HttpClient
instance). I've found no examples of SOCKS5 usage with HC4. How can I use it?

- 839
- 9
- 19

- 203
- 1
- 2
- 4
4 Answers
SOCK is a TCP/IP level proxy protocol, not HTTP. It is not supported by HttpClient out of the box.
One can customize HttpClient to establish connections via a SOCKS proxy by using a custom connection socket factory
EDIT: changes to SSL instead of plain sockets
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);
}
}
-
OK, thanks. I've tried to use this code, but my target host is https. If I pass "https" as scheme to HttpHost constructor, it says that scheme 'https' is not supported. If I pass "http" it says that response is invalid (because it's SSL, I think) – user3511070 Apr 09 '14 at 18:17
-
Does not make a big difference, given SOCKS is a TCP/IP level protocol. See updated code snippets – ok2c Apr 09 '14 at 18:36
-
The context variable is unused as far as I can see? – fragorl Feb 17 '15 at 11:10
-
2`HttpClientContext context = HttpClientContext.create();` is unused – soulmachine Sep 03 '15 at 06:48
-
It's are not doing a https request: `HttpHost target = new HttpHost("localhost", 80, "http");` – Robinho Mar 09 '16 at 18:11
-
CloseableHttpResponse response = httpclient.execute(target, request); needs to be CloseableHttpResponse response = httpclient.execute(target, request, context); – gaurav Aug 30 '16 at 17:46
-
1Directly copy paste your code and it doesn't work for me at first. then I realize in my case I have to register a customized SOCK based `PlainConnectionSocketFactory` on `http` and then it works. Post it here in case someone else has the same problem. – qqibrow Oct 05 '16 at 00:02
-
1Is it possible to user socks5 proxy with authentication? I tried to use `CredentialsProvider` but with no success. Seems like httpclient ignores provided credentials and uses my local username instead. – Emptyfruit Jul 10 '18 at 13:17
-
1this is incorrect: ".register("http", PlainConnectionSocketFactory.INSTANCE)". You have to register a plain socket that overrides connectSocket, just like for https. See answer by b10y – Sergio Mar 30 '19 at 07:43
The answer above works pretty well, unless your country poisons DNS records as well. It is very difficult to say Java "do not use my DNS servers while connecting through proxy" as addressed in these two questions:
java runtime 6 with socks v5 proxy - Possible?
How to get URL connection using proxy in java?
It is also difficult for Apache HttpClient, since it also tries to resolve host names locally. By some modification to the code above, this can be dealt with:
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();
}
}
-
6This was one of the most useful pieces of information on SO I have ever found. Anyone who wants to use HttpClient through a SOCKS proxy and needs to let the SOCKS proxy resolve hostnames, this is the way to do it. – Stephen Roebuck Sep 18 '17 at 13:50
-
@b10y I am getting an error in the code. I have posted a new question https://stackoverflow.com/questions/56030901/unknownhostexception-host-is-unresolved-when-using-socks-proxy-with-http-clie. Can you please have a look? – tarun14110 May 07 '19 at 21:49
Inspired by @oleg's answer. You can make a utility that gives you a properly configured CloseableHttpClient with no special constraints on how you call it.
You can use the ProxySelector in a ConnectionSocketFactory to select the proxy.
A utility class for constructing CloseableHttpClient instances:
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);
}
}
Client code using that:
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()));
}
}
}
}

- 21,033
- 1
- 58
- 84
If you know which URIs need to go to proxy, you can also use the low layer ProxySelector: https://docs.oracle.com/javase/7/docs/technotes/guides/net/proxies.html where for each Socket connection that is made, you can decide what proxies are to be used.
It looks something like this:
public class MyProxySelector extends ProxySelector {
...
public java.util.List<Proxy> select(URI uri) {
...
if (uri is what I need) {
return list of my Proxies
}
...
}
...
}
Then you make use of your selector:
public static void main(String[] args) {
MyProxySelector ps = new MyProxySelector(ProxySelector.getDefault());
ProxySelector.setDefault(ps);
// rest of the application
}

- 4,222
- 1
- 10
- 6