Skip to content

Commit a7030db

Browse files
committed
Make proxy autodetection the default via SystemDefaultRoutePlanner (ProxySelector first, then HTTP(S)_PROXY/NO_PROXY).
Add disableProxyAutodetection() to preserve legacy behavior.
1 parent b38a7b3 commit a7030db

File tree

2 files changed

+112
-5
lines changed

2 files changed

+112
-5
lines changed

httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ private ExecInterceptorEntry(
233233
private boolean authCachingDisabled;
234234
private boolean connectionStateDisabled;
235235
private boolean defaultUserAgentDisabled;
236+
private boolean proxyAutodetectionDisabled;
236237
private ProxySelector proxySelector;
237238

238239
private List<Closeable> closeables;
@@ -791,6 +792,19 @@ public final HttpClientBuilder disableDefaultUserAgent() {
791792
return this;
792793
}
793794

795+
/**
796+
* Disables automatic proxy detection for clients created by this builder.
797+
* <p>
798+
* When disabled, and unless an explicit proxy or route planner is configured,
799+
* the builder falls back to {@link org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner}.
800+
* </p>
801+
* @return this instance.
802+
*/
803+
public final HttpClientBuilder disableProxyAutodetection() {
804+
this.proxyAutodetectionDisabled = true;
805+
return this;
806+
}
807+
794808
/**
795809
* Sets the {@link ProxySelector} that will be used to select the proxies
796810
* to be used for establishing HTTP connections. If a non-null proxy selector is set,
@@ -1012,11 +1026,11 @@ public CloseableHttpClient build() {
10121026
}
10131027
if (proxy != null) {
10141028
routePlannerCopy = new DefaultProxyRoutePlanner(proxy, schemePortResolverCopy);
1015-
} else if (this.proxySelector != null) {
1016-
routePlannerCopy = new SystemDefaultRoutePlanner(schemePortResolverCopy, this.proxySelector);
1017-
} else if (systemProperties) {
1018-
final ProxySelector defaultProxySelector = AccessController.doPrivileged((PrivilegedAction<ProxySelector>) ProxySelector::getDefault);
1019-
routePlannerCopy = new SystemDefaultRoutePlanner(schemePortResolverCopy, defaultProxySelector);
1029+
} else if (!this.proxyAutodetectionDisabled) {
1030+
final ProxySelector effectiveSelector = this.proxySelector != null
1031+
? this.proxySelector
1032+
: AccessController.doPrivileged((PrivilegedAction<ProxySelector>) ProxySelector::getDefault);
1033+
routePlannerCopy = new SystemDefaultRoutePlanner(schemePortResolverCopy, effectiveSelector);
10201034
} else {
10211035
routePlannerCopy = new DefaultRoutePlanner(schemePortResolverCopy);
10221036
}

httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestHttpClientBuilder.java

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,22 @@
2727
package org.apache.hc.client5.http.impl.classic;
2828

2929
import java.io.IOException;
30+
import java.lang.reflect.Field;
31+
import java.net.ProxySelector;
3032

3133
import org.apache.hc.client5.http.classic.ExecChain;
3234
import org.apache.hc.client5.http.classic.ExecChainHandler;
35+
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
36+
import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
37+
import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
38+
import org.apache.hc.client5.http.protocol.HttpClientContext;
39+
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
3340
import org.apache.hc.core5.http.ClassicHttpRequest;
3441
import org.apache.hc.core5.http.ClassicHttpResponse;
3542
import org.apache.hc.core5.http.HttpException;
43+
import org.apache.hc.core5.http.HttpHost;
44+
import org.apache.hc.core5.http.protocol.HttpContext;
45+
import org.junit.jupiter.api.Assertions;
3646
import org.junit.jupiter.api.Test;
3747

3848
class TestHttpClientBuilder {
@@ -66,4 +76,87 @@ public ClassicHttpResponse execute(
6676
return chain.proceed(request, scope);
6777
}
6878
}
79+
80+
@Test
81+
void testDefaultUsesSystemDefaultRoutePlanner() throws Exception {
82+
try (final InternalHttpClient client = (InternalHttpClient) HttpClients.custom().build()) {
83+
final Object planner = getPrivateField(client, "routePlanner");
84+
Assertions.assertNotNull(planner);
85+
Assertions.assertInstanceOf(SystemDefaultRoutePlanner.class, planner, "Default should be SystemDefaultRoutePlanner (auto-detect proxies)");
86+
}
87+
}
88+
89+
@Test
90+
void testDisableProxyAutodetectionFallsBackToDefaultRoutePlanner() throws Exception {
91+
try (final InternalHttpClient client = (InternalHttpClient) HttpClients.custom()
92+
.disableProxyAutodetection()
93+
.build()) {
94+
final Object planner = getPrivateField(client, "routePlanner");
95+
Assertions.assertNotNull(planner);
96+
Assertions.assertInstanceOf(DefaultRoutePlanner.class, planner, "disableProxyAutodetection() should restore DefaultRoutePlanner");
97+
}
98+
}
99+
100+
@Test
101+
void testExplicitProxyWinsOverAutodetection() throws Exception {
102+
try (final InternalHttpClient client = (InternalHttpClient) HttpClients.custom()
103+
.setProxy(new HttpHost("http", "proxy.local", 8080))
104+
.build()) {
105+
final Object planner = getPrivateField(client, "routePlanner");
106+
Assertions.assertNotNull(planner);
107+
Assertions.assertInstanceOf(DefaultProxyRoutePlanner.class, planner, "Explicit proxy must take precedence");
108+
}
109+
}
110+
111+
@Test
112+
void testCustomRoutePlannerIsRespected() throws Exception {
113+
final HttpRoutePlanner custom = new HttpRoutePlanner() {
114+
@Override
115+
public org.apache.hc.client5.http.HttpRoute determineRoute(
116+
final HttpHost host, final HttpContext context) {
117+
// trivial, never used in this test
118+
return new org.apache.hc.client5.http.HttpRoute(host);
119+
}
120+
};
121+
try (final InternalHttpClient client = (InternalHttpClient) HttpClients.custom()
122+
.setRoutePlanner(custom)
123+
.build()) {
124+
final Object planner = getPrivateField(client, "routePlanner");
125+
Assertions.assertSame(custom, planner, "Custom route planner must be used as-is");
126+
}
127+
}
128+
129+
@Test
130+
void testProvidedProxySelectorIsUsedBySystemDefaultRoutePlanner() throws Exception {
131+
class TouchProxySelector extends ProxySelector {
132+
volatile boolean touched = false;
133+
@Override
134+
public java.util.List<java.net.Proxy> select(final java.net.URI uri) {
135+
touched = true;
136+
return java.util.Collections.singletonList(java.net.Proxy.NO_PROXY);
137+
}
138+
@Override
139+
public void connectFailed(final java.net.URI uri, final java.net.SocketAddress sa, final IOException ioe) { }
140+
}
141+
final TouchProxySelector selector = new TouchProxySelector();
142+
143+
try (final InternalHttpClient client = (InternalHttpClient) HttpClients.custom()
144+
.setProxySelector(selector)
145+
.build()) {
146+
final Object planner = getPrivateField(client, "routePlanner");
147+
Assertions.assertInstanceOf(SystemDefaultRoutePlanner.class, planner);
148+
149+
// Call determineRoute on the planner directly to avoid making a real request
150+
final SystemDefaultRoutePlanner sdrp = (SystemDefaultRoutePlanner) planner;
151+
sdrp.determineRoute(new HttpHost("http", "example.com", 80), HttpClientContext.create());
152+
153+
Assertions.assertTrue(selector.touched, "Provided ProxySelector should be consulted");
154+
}
155+
}
156+
157+
private static Object getPrivateField(final Object target, final String name) throws Exception {
158+
final Field f = target.getClass().getDeclaredField(name);
159+
f.setAccessible(true);
160+
return f.get(target);
161+
}
69162
}

0 commit comments

Comments
 (0)