Skip to content

Commit 6818807

Browse files
authored
OpenFeignGH-801: Adding support for JDK Proxy (OpenFeign#1045)
Fixes OpenFeign#801 Adding a `Proxied` client implementation that extends the `Default` client allowing for a JDK Proxy, along with explict credential support.
1 parent 1a4474e commit 6818807

3 files changed

Lines changed: 138 additions & 35 deletions

File tree

core/src/main/java/feign/Client.java

Lines changed: 91 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,20 @@
1717
import static feign.Util.CONTENT_LENGTH;
1818
import static feign.Util.ENCODING_DEFLATE;
1919
import static feign.Util.ENCODING_GZIP;
20+
import static feign.Util.checkArgument;
21+
import static feign.Util.checkNotNull;
22+
import static feign.Util.isNotBlank;
2023
import static java.lang.String.format;
2124

2225
import feign.Request.Options;
2326
import java.io.IOException;
2427
import java.io.InputStream;
2528
import java.io.OutputStream;
2629
import java.net.HttpURLConnection;
30+
import java.net.Proxy;
2731
import java.net.URL;
32+
import java.nio.charset.StandardCharsets;
33+
import java.util.Base64;
2834
import java.util.Collection;
2935
import java.util.LinkedHashMap;
3036
import java.util.List;
@@ -65,9 +71,51 @@ public Response execute(Request request, Options options) throws IOException {
6571
return convertResponse(connection, request);
6672
}
6773

74+
Response convertResponse(HttpURLConnection connection, Request request) throws IOException {
75+
int status = connection.getResponseCode();
76+
String reason = connection.getResponseMessage();
77+
78+
if (status < 0) {
79+
throw new IOException(
80+
format(
81+
"Invalid status(%s) executing %s %s",
82+
status, connection.getRequestMethod(), connection.getURL()));
83+
}
84+
85+
Map<String, Collection<String>> headers = new LinkedHashMap<>();
86+
for (Map.Entry<String, List<String>> field : connection.getHeaderFields().entrySet()) {
87+
// response message
88+
if (field.getKey() != null) {
89+
headers.put(field.getKey(), field.getValue());
90+
}
91+
}
92+
93+
Integer length = connection.getContentLength();
94+
if (length == -1) {
95+
length = null;
96+
}
97+
InputStream stream;
98+
if (status >= 400) {
99+
stream = connection.getErrorStream();
100+
} else {
101+
stream = connection.getInputStream();
102+
}
103+
return Response.builder()
104+
.status(status)
105+
.reason(reason)
106+
.headers(headers)
107+
.request(request)
108+
.body(stream, length)
109+
.build();
110+
}
111+
112+
public HttpURLConnection getConnection(final URL url) throws IOException {
113+
return (HttpURLConnection) url.openConnection();
114+
}
115+
68116
HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
69-
final HttpURLConnection connection =
70-
(HttpURLConnection) new URL(request.url()).openConnection();
117+
final URL url = new URL(request.url());
118+
final HttpURLConnection connection = this.getConnection(url);
71119
if (connection instanceof HttpsURLConnection) {
72120
HttpsURLConnection sslCon = (HttpsURLConnection) connection;
73121
if (sslContextFactory != null) {
@@ -135,43 +183,52 @@ HttpURLConnection convertAndSend(Request request, Options options) throws IOExce
135183
}
136184
return connection;
137185
}
186+
}
187+
188+
/** Client that supports a {@link java.net.Proxy}. */
189+
class Proxied extends Default {
190+
191+
public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
192+
private final Proxy proxy;
193+
private String credentials;
194+
195+
public Proxied(
196+
SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier, Proxy proxy) {
197+
super(sslContextFactory, hostnameVerifier);
198+
checkNotNull(proxy, "a proxy is required.");
199+
this.proxy = proxy;
200+
}
138201

139-
Response convertResponse(HttpURLConnection connection, Request request) throws IOException {
140-
int status = connection.getResponseCode();
141-
String reason = connection.getResponseMessage();
202+
public Proxied(
203+
SSLSocketFactory sslContextFactory,
204+
HostnameVerifier hostnameVerifier,
205+
Proxy proxy,
206+
String proxyUser,
207+
String proxyPassword) {
208+
this(sslContextFactory, hostnameVerifier, proxy);
209+
checkArgument(isNotBlank(proxyUser), "proxy user is required.");
210+
checkArgument(isNotBlank(proxyPassword), "proxy password is required.");
211+
this.credentials = basic(proxyUser, proxyPassword);
212+
}
142213

143-
if (status < 0) {
144-
throw new IOException(
145-
format(
146-
"Invalid status(%s) executing %s %s",
147-
status, connection.getRequestMethod(), connection.getURL()));
214+
@Override
215+
public HttpURLConnection getConnection(URL url) throws IOException {
216+
HttpURLConnection connection = (HttpURLConnection) url.openConnection(this.proxy);
217+
if (isNotBlank(this.credentials)) {
218+
connection.addRequestProperty(PROXY_AUTHORIZATION, this.credentials);
148219
}
220+
return connection;
221+
}
149222

150-
Map<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
151-
for (Map.Entry<String, List<String>> field : connection.getHeaderFields().entrySet()) {
152-
// response message
153-
if (field.getKey() != null) {
154-
headers.put(field.getKey(), field.getValue());
155-
}
156-
}
223+
public String getCredentials() {
224+
return this.credentials;
225+
}
157226

158-
Integer length = connection.getContentLength();
159-
if (length == -1) {
160-
length = null;
161-
}
162-
InputStream stream;
163-
if (status >= 400) {
164-
stream = connection.getErrorStream();
165-
} else {
166-
stream = connection.getInputStream();
167-
}
168-
return Response.builder()
169-
.status(status)
170-
.reason(reason)
171-
.headers(headers)
172-
.request(request)
173-
.body(stream, length)
174-
.build();
227+
private String basic(String username, String password) {
228+
String token = username + ":" + password;
229+
byte[] bytes = token.getBytes(StandardCharsets.ISO_8859_1);
230+
String encoded = Base64.getEncoder().encodeToString(bytes);
231+
return "Basic " + encoded;
175232
}
176233
}
177234
}

core/src/test/java/feign/client/DefaultClientTest.java

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,23 @@
1313
*/
1414
package feign.client;
1515

16+
import static org.assertj.core.api.Assertions.assertThat;
1617
import static org.hamcrest.core.Is.isA;
1718
import static org.junit.Assert.assertEquals;
1819

1920
import feign.Client;
21+
import feign.Client.Proxied;
2022
import feign.Feign;
2123
import feign.Feign.Builder;
2224
import feign.RetryableException;
2325
import java.io.IOException;
26+
import java.net.HttpURLConnection;
27+
import java.net.InetSocketAddress;
2428
import java.net.ProtocolException;
29+
import java.net.Proxy;
30+
import java.net.Proxy.Type;
31+
import java.net.SocketAddress;
32+
import java.net.URL;
2533
import javax.net.ssl.HostnameVerifier;
2634
import javax.net.ssl.SSLSession;
2735
import okhttp3.mockwebserver.MockResponse;
@@ -31,7 +39,7 @@
3139
/** Tests client-specific behavior, such as ensuring Content-Length is sent when specified. */
3240
public class DefaultClientTest extends AbstractClientTest {
3341

34-
Client disableHostnameVerification =
42+
protected Client disableHostnameVerification =
3543
new Client.Default(
3644
TrustingSSLSocketFactory.get(),
3745
new HostnameVerifier() {
@@ -104,4 +112,39 @@ public void canOverrideHostnameVerifier() throws IOException, InterruptedExcepti
104112

105113
api.post("foo");
106114
}
115+
116+
private final SocketAddress proxyAddress = new InetSocketAddress("proxy.example.com", 8080);
117+
118+
/**
119+
* Test that the proxy is being used, but don't check the credentials. Credentials can still be
120+
* used, but they must be set using the appropriate system properties and testing that is not what
121+
* we are looking to do here.
122+
*/
123+
@Test
124+
public void canCreateWithImplicitOrNoCredentials() throws Exception {
125+
Proxied proxied =
126+
new Proxied(TrustingSSLSocketFactory.get(), null, new Proxy(Type.HTTP, proxyAddress));
127+
assertThat(proxied).isNotNull();
128+
assertThat(proxied.getCredentials()).isNullOrEmpty();
129+
130+
/* verify that the proxy */
131+
HttpURLConnection connection = proxied.getConnection(new URL("http://www.example.com"));
132+
assertThat(connection).isNotNull().isInstanceOf(HttpURLConnection.class);
133+
}
134+
135+
@Test
136+
public void canCreateWithExplicitCredentials() throws Exception {
137+
Proxied proxied =
138+
new Proxied(
139+
TrustingSSLSocketFactory.get(),
140+
null,
141+
new Proxy(Type.HTTP, proxyAddress),
142+
"user",
143+
"password");
144+
assertThat(proxied).isNotNull();
145+
assertThat(proxied.getCredentials()).isNotBlank();
146+
147+
HttpURLConnection connection = proxied.getConnection(new URL("http://www.example.com"));
148+
assertThat(connection).isNotNull().isInstanceOf(HttpURLConnection.class);
149+
}
107150
}

pom.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,8 +495,11 @@
495495
<exclude>**/.idea/**</exclude>
496496
<exclude>**/target/**</exclude>
497497
<exclude>LICENSE</exclude>
498+
<exclude>NOTICE</exclude>
499+
<exclude>OSSMETADATA</exclude>
498500
<exclude>**/*.md</exclude>
499501
<exclude>bnd.bnd</exclude>
502+
<exclude>travis/**</exclude>
500503
<exclude>src/test/resources/**</exclude>
501504
<exclude>src/main/resources/**</exclude>
502505
</excludes>

0 commit comments

Comments
 (0)