Details
diff --git a/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java b/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
index 6cfeafd..7e6b4c1 100755
--- a/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
+++ b/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
@@ -36,11 +36,25 @@ import org.keycloak.truststore.TruststoreProvider;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.concurrent.TimeUnit;
/**
+ * The default {@link HttpClientFactory} for {@link HttpClientProvider HttpClientProvider's} used by Keycloak for outbound HTTP calls.
+ * <p>
+ * The constructed clients can be configured via Keycloaks SPI configuration, e.g. {@code standalone.xml, standalone-ha.xml, domain.xml}.
+ * </p>
+ * <p>
+ * Examples for jboss-cli
+ * </p>
+ * <pre>
+ * {@code
+ *
+ * /subsystem=keycloak-server/spi=connectionsHttpClient/provider=default:add(enabled=true)
+ * /subsystem=keycloak-server/spi=connectionsHttpClient/provider=default:write-attribute(name=properties.connection-pool-size,value=128)
+ * /subsystem=keycloak-server/spi=connectionsHttpClient/provider=default:write-attribute(name=properties.proxy-mappings,value=[".*\\.(google|googleapis)\\.com;http://www-proxy.acme.corp.com:8080",".*\\.acme\\.corp\\.com;NO_PROXY",".*;http://fallback:8080"])
+ * }
+ * </pre>
+ * </p>
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class DefaultHttpClientFactory implements HttpClientFactory {
@@ -148,7 +162,7 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
.connectionTTL(connectionTTL, TimeUnit.MILLISECONDS)
.maxConnectionIdleTime(maxConnectionIdleTime, TimeUnit.MILLISECONDS)
.disableCookies(disableCookies)
- .proxyMapping(new ProxyMapping(proxyMappings == null ? Collections.emptyList() : Arrays.asList(proxyMappings)));
+ .proxyMappings(ProxyMappings.valueOf(proxyMappings));
if (disableTrustManager) {
// TODO: is it ok to do away with disabling trust manager?
diff --git a/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java b/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
index 22e8c58..e5bebf2 100755
--- a/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
+++ b/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
@@ -104,7 +104,7 @@ public class HttpClientBuilder {
protected long establishConnectionTimeout = -1;
protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
protected boolean disableCookies = false;
- protected ProxyMapping proxyMapping;
+ protected ProxyMappings proxyMappings;
/**
* Socket inactivity timeout
@@ -208,8 +208,8 @@ public class HttpClientBuilder {
return this;
}
- public HttpClientBuilder proxyMapping(ProxyMapping proxyMapping) {
- this.proxyMapping = proxyMapping;
+ public HttpClientBuilder proxyMappings(ProxyMappings proxyMappings) {
+ this.proxyMappings = proxyMappings;
return this;
}
@@ -290,8 +290,8 @@ public class HttpClientBuilder {
.setConnectionTimeToLive(connectionTTL, connectionTTLUnit);
- if (proxyMapping != null && !proxyMapping.isEmpty()) {
- builder.setRoutePlanner(new ProxyMappingAwareRoutePlanner(proxyMapping));
+ if (proxyMappings != null && !proxyMappings.isEmpty()) {
+ builder.setRoutePlanner(new ProxyMappingsAwareRoutePlanner(proxyMappings));
}
if (maxConnectionIdleTime > 0) {
diff --git a/services/src/main/java/org/keycloak/connections/httpclient/ProxyMappings.java b/services/src/main/java/org/keycloak/connections/httpclient/ProxyMappings.java
new file mode 100644
index 0000000..c534cb8
--- /dev/null
+++ b/services/src/main/java/org/keycloak/connections/httpclient/ProxyMappings.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.connections.httpclient;
+
+import org.apache.http.HttpHost;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * {@link ProxyMappings} describes an ordered mapping for hostname regex patterns to a {@link HttpHost} proxy.
+ * <p>
+ * Mappings can be created via {@link #valueOf(String...)} or {@link #valueOf(List)}.
+ * For a description of the mapping format see {@link ProxyMapping#valueOf(String)}
+ *
+ * @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
+ */
+public class ProxyMappings {
+
+ private static final ProxyMappings EMPTY_MAPPING = valueOf(Collections.emptyList());
+
+ private final List<ProxyMapping> entries;
+
+ /**
+ * Creates a {@link ProxyMappings} from the provided {@link ProxyMapping Entries}.
+ *
+ * @param entries
+ */
+ public ProxyMappings(List<ProxyMapping> entries) {
+ this.entries = Collections.unmodifiableList(entries);
+ }
+
+ /**
+ * Creates a new {@link ProxyMappings} from the provided {@code List} of proxy mapping strings.
+ * <p>
+ *
+ * @param proxyMappings
+ */
+ public static ProxyMappings valueOf(List<String> proxyMappings) {
+
+ if (proxyMappings == null || proxyMappings.isEmpty()) {
+ return EMPTY_MAPPING;
+ }
+
+ List<ProxyMapping> entries = proxyMappings.stream() //
+ .map(ProxyMapping::valueOf) //
+ .collect(Collectors.toList());
+
+ return new ProxyMappings(entries);
+ }
+
+ /**
+ * Creates a new {@link ProxyMappings} from the provided {@code String[]} of proxy mapping strings.
+ *
+ * @param proxyMappings
+ * @return
+ * @see #valueOf(List)
+ * @see ProxyMapping#valueOf(String...)
+ */
+ public static ProxyMappings valueOf(String... proxyMappings) {
+
+ if (proxyMappings == null || proxyMappings.length == 0) {
+ return EMPTY_MAPPING;
+ }
+
+ return valueOf(Arrays.asList(proxyMappings));
+ }
+
+
+ public boolean isEmpty() {
+ return this.entries.isEmpty();
+ }
+
+ /**
+ * @param hostname
+ * @return the {@link HttpHost} proxy associated with the first matching hostname {@link Pattern}
+ * or {@literal null} if none matches.
+ */
+ public HttpHost getProxyFor(String hostname) {
+
+ Objects.requireNonNull(hostname, "hostname");
+
+ return entries.stream() //
+ .filter(e -> e.matches(hostname)) //
+ .findFirst() //
+ .map(ProxyMapping::getProxy) //
+ .orElse(null);
+ }
+
+ /**
+ * {@link ProxyMapping} describes a Proxy Mapping with a Hostname {@link Pattern}
+ * that is mapped to a proxy {@link HttpHost}.
+ */
+ public static class ProxyMapping {
+
+ public static final String NO_PROXY = "NO_PROXY";
+ private static final String DELIMITER = ";";
+
+ private final Pattern hostnamePattern;
+
+ private final HttpHost proxy;
+
+ public ProxyMapping(Pattern hostnamePattern, HttpHost proxy) {
+ this.hostnamePattern = hostnamePattern;
+ this.proxy = proxy;
+ }
+
+ public Pattern getHostnamePattern() {
+ return hostnamePattern;
+ }
+
+ public HttpHost getProxy() {
+ return proxy;
+ }
+
+ public boolean matches(String hostname) {
+ return getHostnamePattern().matcher(hostname).matches();
+ }
+
+ /**
+ * Parses a mapping string into an {@link ProxyMapping}.
+ * <p>
+ * A proxy mapping string must have the following format: {@code hostnameRegex;www-proxy-uri}
+ * with semicolon as a delimiter.</p>
+ * <p>
+ * If no proxy should be used for a host pattern then use {@code NO_PROXY} as www-proxy-uri.
+ * </p>
+ * <p>Examples:
+ * <pre>
+ * {@code
+ *
+ * .*\.(google\.com|googleapis\.com);http://www-proxy.acme.corp.com:8080
+ * .*\.acme\.corp\.com;NO_PROXY
+ * .*;http://fallback:8080
+ * }
+ * </pre>
+ * </p>
+ *
+ * @param mapping
+ * @return
+ */
+ public static ProxyMapping valueOf(String mapping) {
+
+ String[] mappingTokens = mapping.split(DELIMITER);
+
+ String hostPatternRegex = mappingTokens[0];
+ String proxyUriString = mappingTokens[1];
+
+ Pattern hostPattern = Pattern.compile(hostPatternRegex);
+ HttpHost proxyHost = toProxyHost(proxyUriString);
+
+ return new ProxyMapping(hostPattern, proxyHost);
+ }
+
+ private static HttpHost toProxyHost(String proxyUriString) {
+
+ if (NO_PROXY.equals(proxyUriString)) {
+ return null;
+ }
+
+ URI uri = URI.create(proxyUriString);
+ return new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
+ }
+
+ @Override
+ public String toString() {
+ return "ProxyMapping{" +
+ "hostnamePattern=" + hostnamePattern +
+ ", proxy=" + proxy +
+ '}';
+ }
+ }
+}