Skip to content

Commit

Permalink
KEYCLOAK-2623 Remove auth-server-url-for-backend-requests from adapters
Browse files Browse the repository at this point in the history
  • Loading branch information
mposolda committed Apr 5, 2016
1 parent a4335c3 commit 65dc7dd
Show file tree
Hide file tree
Showing 19 changed files with 49 additions and 177 deletions.
Expand Up @@ -169,11 +169,7 @@ public DeploymentDelegate(KeycloakDeployment delegate) {
public void setAuthServerBaseUrl(String authServerBaseUrl) { public void setAuthServerBaseUrl(String authServerBaseUrl) {
this.authServerBaseUrl = authServerBaseUrl; this.authServerBaseUrl = authServerBaseUrl;
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl); KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
resolveBrowserUrls(serverBuilder); resolveUrls(serverBuilder);

if (delegate.getRelativeUrls() == RelativeUrlsUsed.ALL_REQUESTS) {
resolveNonBrowserUrls(serverBuilder);
}
} }


@Override @Override
Expand Down
Expand Up @@ -39,34 +39,14 @@ public static String generateId() {
} }


/** /**
* Best effort to find origin for REST request calls from web UI application to REST application. In case of relative or absolute * Find origin for REST request calls from web UI application to REST application (assuming the REST application
* "auth-server-url" is returned the URL from request. In case of "auth-server-url-for-backend-request" used in configuration, it returns * is deployed on same host like current UI application)
* the origin of auth server.
*
* This may be the optimization in cluster, so if you have keycloak and applications on same host, the REST request doesn't need to
* go through loadbalancer, but can be sent directly to same host.
* *
* @param browserRequestURL * @param browserRequestURL
* @param session
* @return * @return
*/ */
public static String getOriginForRestCalls(String browserRequestURL, KeycloakSecurityContext session) { public static String getOriginForRestCalls(String browserRequestURL) {
if (session instanceof RefreshableKeycloakSecurityContext) { return UriUtils.getOrigin(browserRequestURL);
KeycloakDeployment deployment = ((RefreshableKeycloakSecurityContext)session).getDeployment();
switch (deployment.getRelativeUrls()) {
case ALL_REQUESTS:
case NEVER:
// Resolve baseURI from the request
return UriUtils.getOrigin(browserRequestURL);
case BROWSER_ONLY:
// Resolve baseURI from the codeURL (This is already non-relative and based on our hostname)
return UriUtils.getOrigin(deployment.getTokenUrl());
default:
return "";
}
} else {
return UriUtils.getOrigin(browserRequestURL);
}
} }


public static Set<String> getRolesFromSecurityContext(RefreshableKeycloakSecurityContext session) { public static Set<String> getRolesFromSecurityContext(RefreshableKeycloakSecurityContext session) {
Expand Down
Expand Up @@ -111,39 +111,17 @@ public String getAuthServerBaseUrl() {


public void setAuthServerBaseUrl(AdapterConfig config) { public void setAuthServerBaseUrl(AdapterConfig config) {
this.authServerBaseUrl = config.getAuthServerUrl(); this.authServerBaseUrl = config.getAuthServerUrl();
String authServerURLForBackendReqs = config.getAuthServerUrlForBackendRequests(); if (authServerBaseUrl == null) return;
if (authServerBaseUrl == null && authServerURLForBackendReqs == null) return;


URI authServerUri = null; URI authServerUri = URI.create(authServerBaseUrl);
if (authServerBaseUrl != null) {
authServerUri = URI.create(authServerBaseUrl);
}


if (authServerUri == null || authServerUri.getHost() == null) { if (authServerUri.getHost() == null) {
if (authServerURLForBackendReqs != null) { relativeUrls = RelativeUrlsUsed.ALWAYS;
relativeUrls = RelativeUrlsUsed.BROWSER_ONLY;

KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerURLForBackendReqs);
if (serverBuilder.getHost() == null || serverBuilder.getScheme() == null) {
throw new IllegalStateException("Relative URL not supported for auth-server-url-for-backend-requests option. URL used: "
+ authServerURLForBackendReqs + ", Client: " + config.getResource());
}
resolveNonBrowserUrls(serverBuilder);
} else {
relativeUrls = RelativeUrlsUsed.ALL_REQUESTS;
}
} else { } else {
// We have absolute URI in config // We have absolute URI in config
relativeUrls = RelativeUrlsUsed.NEVER; relativeUrls = RelativeUrlsUsed.NEVER;
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl); KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
resolveBrowserUrls(serverBuilder); resolveUrls(serverBuilder);

if (authServerURLForBackendReqs == null) {
resolveNonBrowserUrls(serverBuilder);
} else {
serverBuilder = KeycloakUriBuilder.fromUri(authServerURLForBackendReqs);
resolveNonBrowserUrls(serverBuilder);
}
} }
} }


Expand All @@ -152,23 +130,14 @@ public void setAuthServerBaseUrl(AdapterConfig config) {
/** /**
* @param authUrlBuilder absolute URI * @param authUrlBuilder absolute URI
*/ */
protected void resolveBrowserUrls(KeycloakUriBuilder authUrlBuilder) { protected void resolveUrls(KeycloakUriBuilder authUrlBuilder) {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("resolveBrowserUrls"); log.debug("resolveUrls");
} }


String login = authUrlBuilder.clone().path(ServiceUrlConstants.AUTH_PATH).build(getRealm()).toString(); String login = authUrlBuilder.clone().path(ServiceUrlConstants.AUTH_PATH).build(getRealm()).toString();
authUrl = KeycloakUriBuilder.fromUri(login); authUrl = KeycloakUriBuilder.fromUri(login);
realmInfoUrl = authUrlBuilder.clone().path(ServiceUrlConstants.REALM_INFO_PATH).build(getRealm()).toString(); realmInfoUrl = authUrlBuilder.clone().path(ServiceUrlConstants.REALM_INFO_PATH).build(getRealm()).toString();
}

/**
* @param authUrlBuilder absolute URI
*/
protected void resolveNonBrowserUrls(KeycloakUriBuilder authUrlBuilder) {
if (log.isDebugEnabled()) {
log.debug("resolveNonBrowserUrls");
}


tokenUrl = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_PATH).build(getRealm()).toString(); tokenUrl = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_PATH).build(getRealm()).toString();
logoutUrl = KeycloakUriBuilder.fromUri(authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH).build(getRealm()).toString()); logoutUrl = KeycloakUriBuilder.fromUri(authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH).build(getRealm()).toString());
Expand Down
Expand Up @@ -54,7 +54,7 @@ public void load() throws Exception {
assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret")); assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId()); assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal()); assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal());
assertEquals("https://backend:8443/auth/realms/demo/protocol/openid-connect/token", deployment.getTokenUrl()); assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/token", deployment.getTokenUrl());
assertEquals(RelativeUrlsUsed.NEVER, deployment.getRelativeUrls()); assertEquals(RelativeUrlsUsed.NEVER, deployment.getRelativeUrls());
assertTrue(deployment.isAlwaysRefreshToken()); assertTrue(deployment.isAlwaysRefreshToken());
assertTrue(deployment.isRegisterNodeAtStartup()); assertTrue(deployment.isRegisterNodeAtStartup());
Expand Down
Expand Up @@ -24,7 +24,6 @@
"client-keystore": "classpath:/keystore.jks", "client-keystore": "classpath:/keystore.jks",
"client-keystore-password": "storepass", "client-keystore-password": "storepass",
"client-key-password": "keypass", "client-key-password": "keypass",
"auth-server-url-for-backend-requests": "https://backend:8443/auth",
"always-refresh-token": true, "always-refresh-token": true,
"register-node-at-startup": true, "register-node-at-startup": true,
"register-node-period": 1000, "register-node-period": 1000,
Expand Down
Expand Up @@ -33,7 +33,7 @@ public class ServletOAuthClientBuilderTest {
public void testBuilder() { public void testBuilder() {
ServletOAuthClient oauthClient = ServletOAuthClientBuilder.build(getClass().getResourceAsStream("/keycloak.json")); ServletOAuthClient oauthClient = ServletOAuthClientBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
Assert.assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", oauthClient.getDeployment().getAuthUrl().clone().build().toString()); Assert.assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", oauthClient.getDeployment().getAuthUrl().clone().build().toString());
Assert.assertEquals("https://backend:8443/auth/realms/demo/protocol/openid-connect/token", oauthClient.getDeployment().getTokenUrl()); Assert.assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/token", oauthClient.getDeployment().getTokenUrl());
assertEquals(RelativeUrlsUsed.NEVER, oauthClient.getRelativeUrlsUsed()); assertEquals(RelativeUrlsUsed.NEVER, oauthClient.getRelativeUrlsUsed());
Assert.assertEquals("customer-portal", oauthClient.getClientId()); Assert.assertEquals("customer-portal", oauthClient.getClientId());
Assert.assertEquals("234234-234234-234234", oauthClient.getCredentials().get(CredentialRepresentation.SECRET)); Assert.assertEquals("234234-234234-234234", oauthClient.getCredentials().get(CredentialRepresentation.SECRET));
Expand Down
Expand Up @@ -19,7 +19,6 @@
"connection-pool-size": 20, "connection-pool-size": 20,
"disable-trust-manager": true, "disable-trust-manager": true,
"allow-any-hostname": true, "allow-any-hostname": true,
"auth-server-url-for-backend-requests": "https://backend:8443/auth",
"always-refresh-token": true, "always-refresh-token": true,
"register-node-at-startup": true, "register-node-at-startup": true,
"register-node-period": 1000, "register-node-period": 1000,
Expand Down
Expand Up @@ -25,29 +25,10 @@ public enum RelativeUrlsUsed {
/** /**
* Always use relative URI and resolve them later based on browser HTTP request * Always use relative URI and resolve them later based on browser HTTP request
*/ */
ALL_REQUESTS, ALWAYS,

/**
* Use relative Uris just for browser requests and resolve those based on browser HTTP requests.
* Backend request (like refresh token request, codeToToken request etc) will use the URI based on current hostname
*/
BROWSER_ONLY,


/** /**
* Relative Uri not used. Configuration contains absolute URI * Relative Uri not used. Configuration contains absolute URI
*/ */
NEVER; NEVER;

public boolean useRelative(boolean isBrowserReq) {
switch (this) {
case ALL_REQUESTS:
return true;
case NEVER:
return false;
case BROWSER_ONLY:
return isBrowserReq;
default:
return true;
}
}
} }
Expand Up @@ -34,7 +34,7 @@
"connection-pool-size", "connection-pool-size",
"allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password", "allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
"client-keystore", "client-keystore-password", "client-key-password", "client-keystore", "client-keystore-password", "client-key-password",
"auth-server-url-for-backend-requests", "always-refresh-token", "always-refresh-token",
"register-node-at-startup", "register-node-period", "token-store", "principal-attribute" "register-node-at-startup", "register-node-period", "token-store", "principal-attribute"
}) })
public class AdapterConfig extends BaseAdapterConfig { public class AdapterConfig extends BaseAdapterConfig {
Expand All @@ -55,8 +55,6 @@ public class AdapterConfig extends BaseAdapterConfig {
protected String clientKeyPassword; protected String clientKeyPassword;
@JsonProperty("connection-pool-size") @JsonProperty("connection-pool-size")
protected int connectionPoolSize = 20; protected int connectionPoolSize = 20;
@JsonProperty("auth-server-url-for-backend-requests")
protected String authServerUrlForBackendRequests;
@JsonProperty("always-refresh-token") @JsonProperty("always-refresh-token")
protected boolean alwaysRefreshToken = false; protected boolean alwaysRefreshToken = false;
@JsonProperty("register-node-at-startup") @JsonProperty("register-node-at-startup")
Expand Down Expand Up @@ -134,14 +132,6 @@ public void setConnectionPoolSize(int connectionPoolSize) {
this.connectionPoolSize = connectionPoolSize; this.connectionPoolSize = connectionPoolSize;
} }


public String getAuthServerUrlForBackendRequests() {
return authServerUrlForBackendRequests;
}

public void setAuthServerUrlForBackendRequests(String authServerUrlForBackendRequests) {
this.authServerUrlForBackendRequests = authServerUrlForBackendRequests;
}

public boolean isAlwaysRefreshToken() { public boolean isAlwaysRefreshToken() {
return alwaysRefreshToken; return alwaysRefreshToken;
} }
Expand Down
Expand Up @@ -96,6 +96,23 @@


<section> <section>
<title>Version specific migration</title> <title>Version specific migration</title>

<section>
<title>Migrating to 1.9.2</title>
<simplesect>
<title>Adapter option auth-server-url-for-backend-requests removed</title>
<para>
We've removed the option <literal>auth-server-url-for-backend-requests</literal> as there were issues in some scenarios when it was used.
In more details, it was possible to access the Keycloak server from 2 different contexts (internal and external), which was
causing issues in token validations etc.
</para>
<para>
If you still want to use the optimization of network, which this option provided (avoid the application to send backchannel requests
through loadbalancer but send them to local Keycloak server directly) you may need to handle it at hosts configuration (DNS) level.
</para>
</simplesect>
</section>

<section> <section>
<title>Migrating to 1.9.0</title> <title>Migrating to 1.9.0</title>
<simplesect> <simplesect>
Expand Down
Expand Up @@ -102,39 +102,6 @@
</para> </para>
</section> </section>


<section id="relative-uri-optimization">
<title>Relative URI optimization</title>
<para>
In many deployment scenarios will be Keycloak and secured applications deployed on same cluster hosts. For this case Keycloak
already provides option to use relative URI as value of option <emphasis>auth-server-url</emphasis> in <literal>WEB-INF/keycloak.json</literal> .
In this case, the URI of Keycloak server is resolved from the URI of current request.
</para>
<para>
For example if your loadbalancer is on <emphasis>https://loadbalancer.com/myapp</emphasis> and auth-server-url is <emphasis>/auth</emphasis>,
then relative URI of Keycloak is resolved to be <emphasis>https://loadbalancer.com/auth</emphasis> .
</para>
<para>
For cluster setup, it may be even better to use option <emphasis>auth-server-url-for-backend-request</emphasis> . This allows to configure
that backend requests between Keycloak and your application will be sent directly to same cluster host without additional
round-trip through loadbalancer. So for this, it's good to configure values in <literal>WEB-INF/keycloak.json</literal> like this:
<programlisting>
<![CDATA[
"auth-server-url": "/auth",
"auth-server-url-for-backend-requests": "http://${jboss.host.name}:8080/auth"
]]>
</programlisting>
</para>
<para>
This would mean that browser requests (like redirecting to Keycloak login screen) will be still resolved relatively
to current request URI like <emphasis>https://loadbalancer.com/myapp</emphasis>, but backend (out-of-bound) requests between keycloak
and your app are sent always to same cluster host with application .
</para>
<para>
Note that additionally to network optimization,
you may not need "https" in this case as application and keycloak are communicating directly within same cluster host.
</para>
</section>

<section id="admin-url-configuration"> <section id="admin-url-configuration">
<title>Admin URL configuration</title> <title>Admin URL configuration</title>
<para> <para>
Expand Down
Expand Up @@ -30,7 +30,6 @@
import org.keycloak.constants.ServiceUrlConstants; import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.common.util.HostUtils;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.UriUtils; import org.keycloak.common.util.UriUtils;
Expand Down Expand Up @@ -91,7 +90,7 @@ public static AccessTokenResponse getToken(HttpServletRequest request) throws IO




try { try {
HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(getBaseUrl(request) + "/auth") HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(getRequestOrigin(request) + "/auth")
.path(ServiceUrlConstants.TOKEN_PATH).build("demo")); .path(ServiceUrlConstants.TOKEN_PATH).build("demo"));
List <NameValuePair> formparams = new ArrayList <NameValuePair>(); List <NameValuePair> formparams = new ArrayList <NameValuePair>();
formparams.add(new BasicNameValuePair("username", "admin")); formparams.add(new BasicNameValuePair("username", "admin"));
Expand Down Expand Up @@ -124,7 +123,7 @@ public static void logout(HttpServletRequest request, AccessTokenResponse res) t




try { try {
HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(getBaseUrl(request) + "/auth") HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(UriUtils.getOrigin(request.getRequestURL().toString()) + "/auth")
.path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH) .path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
.build("demo")); .build("demo"));
List<NameValuePair> formparams = new ArrayList<NameValuePair>(); List<NameValuePair> formparams = new ArrayList<NameValuePair>();
Expand Down Expand Up @@ -152,7 +151,7 @@ public static List<RoleRepresentation> getRealmRoles(HttpServletRequest request,


HttpClient client = new DefaultHttpClient(); HttpClient client = new DefaultHttpClient();
try { try {
HttpGet get = new HttpGet(getBaseUrl(request) + "/auth/admin/realms/demo/roles"); HttpGet get = new HttpGet(UriUtils.getOrigin(request.getRequestURL().toString()) + "/auth/admin/realms/demo/roles");
get.addHeader("Authorization", "Bearer " + res.getToken()); get.addHeader("Authorization", "Bearer " + res.getToken());
try { try {
HttpResponse response = client.execute(get); HttpResponse response = client.execute(get);
Expand All @@ -174,13 +173,8 @@ public static List<RoleRepresentation> getRealmRoles(HttpServletRequest request,
} }
} }


public static String getBaseUrl(HttpServletRequest request) { public static String getRequestOrigin(HttpServletRequest request) {
String useHostname = request.getServletContext().getInitParameter("useHostname"); return UriUtils.getOrigin(request.getRequestURL().toString());
if (useHostname != null && "true".equalsIgnoreCase(useHostname)) {
return "http://" + HostUtils.getHostName() + ":8080";
} else {
return UriUtils.getOrigin(request.getRequestURL().toString());
}
} }


} }
Expand Up @@ -23,9 +23,4 @@


<module-name>admin-access</module-name> <module-name>admin-access</module-name>


<context-param>
<param-name>useHostname</param-name>
<param-value>false</param-value>
</context-param>

</web-app> </web-app>
Expand Up @@ -23,7 +23,7 @@
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterUtils; import org.keycloak.common.util.UriUtils;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;


Expand Down Expand Up @@ -66,7 +66,7 @@ public static List<String> getCustomers(HttpServletRequest req) throws Failure {


HttpClient client = new DefaultHttpClient(); HttpClient client = new DefaultHttpClient();
try { try {
HttpGet get = new HttpGet(AdapterUtils.getOriginForRestCalls(req.getRequestURL().toString(), session) + "/database/customers"); HttpGet get = new HttpGet(UriUtils.getOrigin(req.getRequestURL().toString()) + "/database/customers");
get.addHeader("Authorization", "Bearer " + session.getTokenString()); get.addHeader("Authorization", "Bearer " + session.getTokenString());
try { try {
HttpResponse response = client.execute(get); HttpResponse response = client.execute(get);
Expand Down
Expand Up @@ -24,6 +24,7 @@
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterUtils; import org.keycloak.adapters.AdapterUtils;
import org.keycloak.common.util.UriUtils;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;


Expand Down Expand Up @@ -59,7 +60,7 @@ public static List<RoleRepresentation> getRealmRoles(HttpServletRequest req) thr


HttpClient client = new DefaultHttpClient(); HttpClient client = new DefaultHttpClient();
try { try {
HttpGet get = new HttpGet(AdapterUtils.getOriginForRestCalls(req.getRequestURL().toString(), session) + "/auth/admin/realms/demo/roles"); HttpGet get = new HttpGet(UriUtils.getOrigin(req.getRequestURL().toString()) + "/auth/admin/realms/demo/roles");
get.addHeader("Authorization", "Bearer " + session.getTokenString()); get.addHeader("Authorization", "Bearer " + session.getTokenString());
try { try {
HttpResponse response = client.execute(get); HttpResponse response = client.execute(get);
Expand Down
Expand Up @@ -23,7 +23,7 @@
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterUtils; import org.keycloak.common.util.UriUtils;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;


Expand Down Expand Up @@ -66,7 +66,7 @@ public static List<String> getCustomers(HttpServletRequest req) throws Failure {


HttpClient client = new DefaultHttpClient(); HttpClient client = new DefaultHttpClient();
try { try {
HttpGet get = new HttpGet(AdapterUtils.getOriginForRestCalls(req.getRequestURL().toString(), session) + "/database/customers"); HttpGet get = new HttpGet(UriUtils.getOrigin(req.getRequestURL().toString()) + "/database/customers");
get.addHeader("Authorization", "Bearer " + session.getTokenString()); get.addHeader("Authorization", "Bearer " + session.getTokenString());
try { try {
HttpResponse response = client.execute(get); HttpResponse response = client.execute(get);
Expand Down

0 comments on commit 65dc7dd

Please sign in to comment.