/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 gobblin.http;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.http.HttpHost;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigValueFactory;
import gobblin.annotation.Alias;
import gobblin.configuration.State;
/**
* Default implementation that uses the following properties to configure an {@link HttpClient}.
*
* <ul>
* <li>{@link #PROXY_HOSTPORT_KEY}
* <li>{@link #PROXY_URL_KEY}
* <li>{@link #PROXY_PORT_KEY}
* </ul>
*/
@Alias(value="default")
public class DefaultHttpClientConfigurator implements HttpClientConfigurator {
// IMPORTANT: don't change the values for PROXY_URL_KEY and PROXY_PORT_KEY as they are meant to
// be backwards compatible with SOURCE_CONN_USE_PROXY_URL and SOURCE_CONN_USE_PROXY_PORT when
// the statePropertiesPrefix is "source.conn."
/** The hostname of the HTTP proxy to use */
public static final String PROXY_URL_KEY = "use.proxy.url";
/** The port of the HTTP proxy to use */
public static final String PROXY_PORT_KEY = "use.proxy.port";
/** Similar to {@link #PROXY_URL_KEY} and {@link #PROXY_PORT_KEY} but allows you to set it on
* one property as <host>:<port> . This property takes precedence over those properties. */
public static final String PROXY_HOSTPORT_KEY = "proxyHostport";
/** Port to use if the HTTP Proxy is enabled but no port is specified */
public static final int DEFAULT_HTTP_PROXY_PORT = 8080;
private static final Pattern HOSTPORT_PATTERN = Pattern.compile("([^:]+)(:([0-9]+))?");
protected final HttpClientBuilder _builder = HttpClientBuilder.create();
protected String _statePropertiesPrefix = null;
/** {@inheritDoc} */
@Override
public DefaultHttpClientConfigurator configure(Config httpClientConfig) {
Optional<HttpHost> proxy = getProxyAddr(httpClientConfig);
if (proxy.isPresent()) {
getBuilder().setProxy(proxy.get());
}
return this;
}
/** {@inheritDoc} */
@Override
public DefaultHttpClientConfigurator configure(State state) {
Config cfg = stateToConfig(state);
return configure(cfg);
}
protected Config stateToConfig(State state) {
String proxyUrlKey = getPrefixedPropertyName(PROXY_URL_KEY);
String proxyPortKey = getPrefixedPropertyName(PROXY_PORT_KEY);
String proxyHostportKey = getPrefixedPropertyName(PROXY_HOSTPORT_KEY);
Config cfg = ConfigFactory.empty();
if (state.contains(proxyUrlKey)) {
cfg = cfg.withValue(PROXY_URL_KEY, ConfigValueFactory.fromAnyRef(state.getProp(proxyUrlKey)));
}
if (state.contains(proxyPortKey)) {
cfg = cfg.withValue(PROXY_PORT_KEY, ConfigValueFactory.fromAnyRef(state.getPropAsInt(proxyPortKey)));
}
if (state.contains(proxyHostportKey)) {
cfg = cfg.withValue(PROXY_HOSTPORT_KEY, ConfigValueFactory.fromAnyRef(state.getProp(proxyHostportKey)));
}
return cfg;
}
/** {@inheritDoc} */
@Override
public CloseableHttpClient createClient() {
return _builder.build();
}
@VisibleForTesting
public static Optional<HttpHost> getProxyAddr(Config httpClientConfig) {
String proxyHost = null;
int proxyPort = DEFAULT_HTTP_PROXY_PORT;
if (httpClientConfig.hasPath(PROXY_URL_KEY) &&
!httpClientConfig.getString(PROXY_URL_KEY).isEmpty()) {
proxyHost = httpClientConfig.getString(PROXY_URL_KEY);
}
if (httpClientConfig.hasPath(PROXY_PORT_KEY)) {
proxyPort = httpClientConfig.getInt(PROXY_PORT_KEY);
}
if (httpClientConfig.hasPath(PROXY_HOSTPORT_KEY)) {
String hostport = httpClientConfig.getString(PROXY_HOSTPORT_KEY);
Matcher hostportMatcher = HOSTPORT_PATTERN.matcher(hostport);
if (!hostportMatcher.matches()) {
throw new IllegalArgumentException("Invalid HTTP proxy hostport: " + hostport);
}
proxyHost = hostportMatcher.group(1);
if (!Strings.isNullOrEmpty(hostportMatcher.group(3))) {
proxyPort = Integer.parseInt(hostportMatcher.group(3));
}
}
return null != proxyHost ? Optional.of(new HttpHost(proxyHost, proxyPort))
: Optional.<HttpHost>absent();
}
@Override
public DefaultHttpClientConfigurator setStatePropertiesPrefix(String propertiesPrefix) {
_statePropertiesPrefix = propertiesPrefix;
return this;
}
String getPrefixedPropertyName(String propertyName) {
return null != _statePropertiesPrefix ? _statePropertiesPrefix + propertyName : propertyName;
}
@Override
public HttpClientBuilder getBuilder() {
return _builder;
}
}