HttpClientConfigurableHttpConnectionFactory.java
/*
* Copyright 2018-2019 the original author or authors.
*
* 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
*
* https://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.springframework.cloud.config.server.environment;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.eclipse.jgit.transport.http.HttpConnection;
import org.eclipse.jgit.transport.http.apache.HttpClientConnection;
import org.springframework.cloud.config.server.support.HttpClient4Support;
import org.springframework.util.ObjectUtils;
import static java.util.stream.Collectors.toMap;
/**
* @author Dylan Roberts
*/
public class HttpClientConfigurableHttpConnectionFactory implements ConfigurableHttpConnectionFactory {
private static final String PLACEHOLDER_PATTERN_STRING = "\\{(\\w+)}";
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile(PLACEHOLDER_PATTERN_STRING);
Log log = LogFactory.getLog(getClass());
Map<String, HttpClientBuilder> httpClientBuildersByUri = new LinkedHashMap<>();
@Override
public void addConfiguration(MultipleJGitEnvironmentProperties environmentProperties)
throws GeneralSecurityException {
addHttpClient(environmentProperties);
for (JGitEnvironmentProperties repo : environmentProperties.getRepos().values()) {
addHttpClient(repo);
}
}
@Override
public HttpConnection create(URL url) throws IOException {
return create(url, null);
}
@Override
public HttpConnection create(URL url, Proxy proxy) throws IOException {
HttpClientBuilder builder = lookupHttpClientBuilder(url);
if (builder != null) {
return new HttpClientConnection(url.toString(), null, builder.build());
}
else {
/*
* No matching builder found: let jGit handle the creation of the HttpClient
*/
return new HttpClientConnection(url.toString());
}
}
private void addHttpClient(JGitEnvironmentProperties properties) throws GeneralSecurityException {
if (properties.getUri() != null && properties.getUri().startsWith("http")) {
this.httpClientBuildersByUri.put(properties.getUri(), HttpClient4Support.builder(properties));
}
}
private HttpClientBuilder lookupHttpClientBuilder(final URL url) {
Map<String, HttpClientBuilder> builderMap = this.httpClientBuildersByUri.entrySet().stream().filter(entry -> {
String key = entry.getKey();
String spec = getUrlWithPlaceholders(url, key);
if (spec.equals(key)) {
return true;
}
int index = spec.lastIndexOf("/");
while (index != -1) {
spec = spec.substring(0, index);
if (spec.equals(key)) {
return true;
}
index = spec.lastIndexOf("/");
}
return false;
}).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
if (builderMap.isEmpty()) {
this.log.warn(String.format("No custom http config found for URL: %s", url));
return null;
}
if (builderMap.size() > 1) {
/*
* Try to determine if there is an exact match URL or not. So if there is a
* placeholder in the URL, filter it out. We should be left with only URLs
* which have no placeholders. That is the one we want to use in the case
* there are multiple matches.
*/
List<String> keys = builderMap.keySet().stream().filter(key -> !PLACEHOLDER_PATTERN.matcher(key).find())
.collect(Collectors.toList());
if (keys.size() == 1) {
return builderMap.get(keys.get(0));
}
this.log.error(String.format(
"More than one git repo URL template matched URL:"
+ " %s, proxy and skipSslValidation config won't be applied. Matched templates: %s",
url, builderMap.keySet().stream().collect(Collectors.joining(", "))));
return null;
}
return new ArrayList<>(builderMap.values()).get(0);
}
private String getUrlWithPlaceholders(URL url, String key) {
String spec = url.toString();
String[] tokens = key.split(PLACEHOLDER_PATTERN_STRING);
// if token[0] equals url then there was no placeholder in the the url, so
// matching needed
if (tokens.length >= 1 && !tokens[0].equals(url.toString())) {
List<Placeholder> placeholders = getPlaceholders(key);
List<String> values = getValues(spec, tokens);
if (placeholders.size() == values.size()) {
for (int i = 0; i < values.size(); i++) {
// if the key does not start with first part of the spec before the
// place holder then its
// not a match
String specBeforePlaceholder = spec.substring(0, placeholders.get(i).start);
if (key.startsWith(specBeforePlaceholder)) {
spec = specBeforePlaceholder + "{" + placeholders.get(i).group + "}"
+ spec.substring(placeholders.get(i).start + values.get(i).length());
}
}
}
}
return spec;
}
private List<String> getValues(String spec, String[] tokens) {
List<String> values = new LinkedList<>();
for (String token : tokens) {
String[] valueTokens = spec.split(token);
if (!ObjectUtils.isEmpty(valueTokens[0])) {
values.add(valueTokens[0]);
}
if (valueTokens.length > 1) {
spec = valueTokens[1];
}
}
if (tokens.length == 1 && !ObjectUtils.isEmpty(spec)) {
values.add(spec);
}
return values;
}
private List<Placeholder> getPlaceholders(String key) {
Pattern pattern = Pattern.compile(PLACEHOLDER_PATTERN_STRING);
Matcher matcher = pattern.matcher(key);
List<Placeholder> placeholders = new LinkedList<>();
while (matcher.find()) {
placeholders.add(new Placeholder(matcher.group(1), matcher.start(), matcher.end()));
}
return placeholders;
}
private static class Placeholder {
String group;
int start;
int end;
Placeholder(String group, int start, int end) {
this.start = start;
this.end = end;
this.group = group;
}
}
}