diff --git a/README.md b/README.md
index 96f8c1d..5351497 100644
--- a/README.md
+++ b/README.md
@@ -103,6 +103,15 @@ If you already have an access token and endpoint (e.g. from a cookie), you can p
ForceApi api = new ForceApi(c,s);
+### Instantiate with proxy
+
+ ForceApi api = new ForceApi(new ApiConfig()
+ .setProxyHost("127.0.0.1")
+ .setProxyPort(8080)
+ .setProxyUsername("proxy-user")
+ .setProxyPassword("proxy-password"));
+
+
## CRUD and Query Operations
### Get an SObject
diff --git a/pom.xml b/pom.xml
index bbac1fe..16fc353 100644
--- a/pom.xml
+++ b/pom.xml
@@ -176,8 +176,8 @@
maven-compiler-plugin
2.3.2
- 8
- 8
+ 9
+ 9
diff --git a/src/main/java/com/force/api/ApiConfig.java b/src/main/java/com/force/api/ApiConfig.java
index be1a24b..014fdef 100644
--- a/src/main/java/com/force/api/ApiConfig.java
+++ b/src/main/java/com/force/api/ApiConfig.java
@@ -3,8 +3,7 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
-import java.net.URI;
-import java.net.URLDecoder;
+import java.net.*;
public class ApiConfig {
@@ -16,6 +15,10 @@ public class ApiConfig {
String clientId;
String clientSecret;
String redirectURI;
+ String proxyHost;
+ Integer proxyPort;
+ String proxyUsername;
+ String proxyPassword;
SessionRefreshListener sessionRefreshListener;
ObjectMapper objectMapper;
int requestTimeout = 0; // in milliseconds, defaults to 0 which is no timeout (infinity)
@@ -35,7 +38,11 @@ public ApiConfig clone() {
.setClientSecret(clientSecret)
.setRedirectURI(redirectURI)
.setObjectMapper(objectMapper)
- .setRequestTimeout(requestTimeout);
+ .setRequestTimeout(requestTimeout)
+ .setProxyHost(proxyHost)
+ .setProxyPort(proxyPort)
+ .setProxyUsername(proxyUsername)
+ .setProxyPassword(proxyPassword);
}
public ApiConfig setForceURL(String url) {
@@ -102,6 +109,37 @@ public ApiConfig setClientSecret(String value) {
clientSecret = value;
return this;
}
+
+ public ApiConfig setProxyHost(String value) {
+ proxyHost = value;
+ return this;
+ }
+
+ public ApiConfig setProxyPort(String value) {
+ try {
+ proxyPort = Integer.parseInt(value.trim());
+ }
+ catch(NullPointerException | NumberFormatException e){
+ proxyPort = null;
+ }
+ return this;
+ }
+
+ public ApiConfig setProxyPort(int value) {
+ proxyPort = value;
+ return this;
+ }
+
+ public ApiConfig setProxyUsername(String value) {
+ proxyUsername = value;
+ return this;
+ }
+
+ public ApiConfig setProxyPassword(String value) {
+ proxyPassword = value;
+ return this;
+ }
+
public ApiConfig setSessionRefreshListener(SessionRefreshListener value) {
sessionRefreshListener = value;
return this;
@@ -147,6 +185,27 @@ public String getRedirectURI() {
return redirectURI;
}
+ public ProxySettings getProxySettings() {
+ ApiConfig config = this;
+ Proxy proxy = null;
+ Authenticator authenticator = null;
+ if(proxyHost != null && proxyPort != null) {
+ proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
+ if(proxyUsername != null && proxyPassword != null) {
+ authenticator = new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ if (getRequestorType() == RequestorType.PROXY && config.proxyUsername != null && config.proxyPassword != null) {
+ return new PasswordAuthentication(config.proxyUsername, config.proxyPassword.toCharArray());
+ }
+ return null;
+ }
+ };
+ }
+ }
+ return new ProxySettings(proxy, authenticator);
+ }
+
public SessionRefreshListener getSessionRefreshListener() { return sessionRefreshListener; }
public ObjectMapper getObjectMapper() { return objectMapper; }
diff --git a/src/main/java/com/force/api/Auth.java b/src/main/java/com/force/api/Auth.java
index 282d269..071749b 100644
--- a/src/main/java/com/force/api/Auth.java
+++ b/src/main/java/com/force/api/Auth.java
@@ -4,10 +4,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLEncoder;
+import java.net.*;
import java.util.Map;
@@ -40,7 +37,8 @@ static public final ApiSession oauthLoginPasswordFlow(ApiConfig c) {
.param("client_id",c.getClientId())
.param("client_secret", c.getClientSecret())
.param("username",c.getUsername())
- .param("password",c.getPassword())
+ .param("password",c.getPassword()),
+ c.getProxySettings()
);
if(r.getResponseCode()!=200) {
throw new AuthException(r.getResponseCode(),"Auth.oauthLoginPasswordFlow failed: "+r.getString());
@@ -79,7 +77,12 @@ static public final ApiSession soaploginPasswordFlow(ApiConfig c) {
if(c.getPassword()==null) throw new IllegalStateException("password cannot be null");
try {
URL url = new URL(c.getLoginEndpoint()+"/services/Soap/u/33.0");
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ Proxy proxy = c.getProxySettings().getProxy();
+ Authenticator authenticator = c.getProxySettings().getProxyAuthenticator();
+ HttpURLConnection conn = (HttpURLConnection) (proxy == null ? url.openConnection() : url.openConnection(proxy));
+ if(proxy != null && authenticator != null) {
+ conn.setAuthenticator(authenticator);
+ }
conn.setDoOutput(true);
conn.addRequestProperty("Content-Type", "text/xml");
conn.addRequestProperty("SOAPAction", "login");
@@ -161,7 +164,8 @@ static public final ApiSession completeOAuthWebServerFlow(AuthorizationResponse
.param("client_id",res.apiConfig.getClientId())
.param("client_secret", res.apiConfig.getClientSecret())
.param("redirect_uri",res.apiConfig.getRedirectURI())
- .preEncodedParam("code",res.code)
+ .preEncodedParam("code",res.code),
+ res.apiConfig.getProxySettings()
);
if(r.getResponseCode()!=200) {
throw new AuthException(r.getResponseCode(),r.getString());
@@ -191,7 +195,8 @@ static public final ApiSession refreshOauthTokenFlow(ApiConfig config, String re
.param("grant_type","refresh_token")
.param("client_id",config.getClientId())
.param("client_secret", config.getClientSecret())
- .param("refresh_token", refreshToken)
+ .param("refresh_token", refreshToken),
+ config.getProxySettings()
);
if(r.getResponseCode()!=200) {
throw new AuthException(r.getResponseCode(),r.getString());
@@ -222,7 +227,8 @@ static public void revokeToken(ApiConfig config, String token) {
Http.send(HttpRequest.formPost()
.header("Accept","*/*")
.url(config.getLoginEndpoint()+"/services/oauth2/revoke")
- .param("token", token));
+ .param("token", token),
+ config.getProxySettings());
} catch(Throwable t) {
// Looks like revoke endpoint closes stream when trying to revoke
// an already revoked token. It doesn't return an error code. So
diff --git a/src/main/java/com/force/api/ForceApi.java b/src/main/java/com/force/api/ForceApi.java
index d9277bf..19d0194 100644
--- a/src/main/java/com/force/api/ForceApi.java
+++ b/src/main/java/com/force/api/ForceApi.java
@@ -68,11 +68,11 @@ public ForceApi(ApiSession session) {
}
public ForceApi(ApiConfig apiConfig) {
+ System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
config = apiConfig;
jsonMapper = config.getObjectMapper();
session = Auth.authenticate(apiConfig);
autoRenew = true;
-
}
public ApiSession getSession() {
@@ -496,7 +496,7 @@ private final String uriBaseOrRoot() {
private final HttpResponse apiRequest(HttpRequest req) {
req.setAuthorization("Bearer "+session.getAccessToken());
req.setRequestTimeout(this.config.getRequestTimeout());
- HttpResponse res = Http.send(req);
+ HttpResponse res = Http.send(req, this.config.getProxySettings());
if(res.getResponseCode()==401) {
// Perform one attempt to auto renew session if possible
if (autoRenew) {
@@ -510,7 +510,7 @@ private final HttpResponse apiRequest(HttpRequest req) {
config.getSessionRefreshListener().sessionRefreshed(session);
}
req.setAuthorization("Bearer "+session.getAccessToken());
- res = Http.send(req);
+ res = Http.send(req, this.config.getProxySettings());
}
}
// 304 is a special case when the "If-Modified-Since" header is used, it is not an error,
diff --git a/src/main/java/com/force/api/ProxySettings.java b/src/main/java/com/force/api/ProxySettings.java
new file mode 100644
index 0000000..1704136
--- /dev/null
+++ b/src/main/java/com/force/api/ProxySettings.java
@@ -0,0 +1,22 @@
+package com.force.api;
+
+import java.net.Authenticator;
+import java.net.Proxy;
+
+public class ProxySettings {
+ private final Proxy proxy;
+ private final Authenticator proxyAuthenticator;
+
+ ProxySettings(Proxy proxy, Authenticator proxyAuthenticator) {
+ this.proxy = proxy;
+ this.proxyAuthenticator = proxyAuthenticator;
+ }
+
+ public Proxy getProxy() {
+ return proxy;
+ }
+
+ public Authenticator getProxyAuthenticator() {
+ return proxyAuthenticator;
+ }
+}
diff --git a/src/main/java/com/force/api/http/Http.java b/src/main/java/com/force/api/http/Http.java
index fdc75ed..512c726 100644
--- a/src/main/java/com/force/api/http/Http.java
+++ b/src/main/java/com/force/api/http/Http.java
@@ -1,5 +1,6 @@
package com.force.api.http;
+import com.force.api.ProxySettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -8,11 +9,7 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
+import java.net.*;
public class Http {
@@ -60,9 +57,14 @@ static final byte[] readResponse(InputStream stream) throws IOException {
return bout.toByteArray();
}
- public static final HttpResponse send(HttpRequest req) {
+ public static final HttpResponse send(HttpRequest req, ProxySettings proxySettings) {
try {
- HttpURLConnection conn = (HttpURLConnection) new URL(req.getUrl()).openConnection();
+ URL url = new URL(req.getUrl());
+ Proxy proxy = proxySettings.getProxy();
+ HttpURLConnection conn = (HttpURLConnection) (proxy == null ? url.openConnection() : url.openConnection(proxy));
+ if(proxy != null && proxySettings.getProxyAuthenticator() != null) {
+ conn.setAuthenticator(proxySettings.getProxyAuthenticator());
+ }
if(req.getRequestTimeout()>0){
conn.setConnectTimeout(req.getRequestTimeout());
conn.setReadTimeout(req.getRequestTimeout());