2020-03-23 15:35:58 +01:00
|
|
|
/*
|
|
|
|
|
* MIT License
|
2015-04-30 07:17:52 +02:00
|
|
|
*
|
2020-03-23 15:35:58 +01:00
|
|
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
2015-04-30 07:17:52 +02:00
|
|
|
*
|
2020-03-23 15:35:58 +01:00
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
* furnished to do so, subject to the following conditions:
|
2015-04-30 07:17:52 +02:00
|
|
|
*
|
2020-03-23 15:35:58 +01:00
|
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
|
|
|
* copies or substantial portions of the Software.
|
2015-04-30 07:17:52 +02:00
|
|
|
*
|
2020-03-23 15:35:58 +01:00
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
|
* SOFTWARE.
|
2015-04-30 07:17:52 +02:00
|
|
|
*/
|
2020-10-26 16:54:05 +01:00
|
|
|
|
2015-04-30 07:17:52 +02:00
|
|
|
package sonia.scm.net.ahc;
|
|
|
|
|
|
|
|
|
|
//~--- non-JDK imports --------------------------------------------------------
|
|
|
|
|
|
2015-05-01 12:36:49 +02:00
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
2015-04-30 07:17:52 +02:00
|
|
|
import com.google.common.base.Strings;
|
|
|
|
|
import com.google.common.collect.Multimap;
|
|
|
|
|
import com.google.common.io.Closeables;
|
|
|
|
|
import com.google.inject.Inject;
|
|
|
|
|
import org.apache.shiro.codec.Base64;
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
import sonia.scm.config.ScmConfiguration;
|
|
|
|
|
import sonia.scm.net.Proxies;
|
|
|
|
|
import sonia.scm.net.TrustAllHostnameVerifier;
|
|
|
|
|
import sonia.scm.net.TrustAllTrustManager;
|
2020-10-26 16:54:05 +01:00
|
|
|
import sonia.scm.trace.Span;
|
|
|
|
|
import sonia.scm.trace.Tracer;
|
2015-04-30 07:17:52 +02:00
|
|
|
import sonia.scm.util.HttpUtil;
|
|
|
|
|
|
2020-10-26 16:54:05 +01:00
|
|
|
import javax.annotation.Nonnull;
|
2017-07-03 16:59:19 +02:00
|
|
|
import javax.inject.Provider;
|
|
|
|
|
import javax.net.ssl.HttpsURLConnection;
|
|
|
|
|
import javax.net.ssl.SSLContext;
|
|
|
|
|
import javax.net.ssl.TrustManager;
|
2015-04-30 07:17:52 +02:00
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.io.OutputStream;
|
2017-07-03 16:59:19 +02:00
|
|
|
import java.net.*;
|
2015-04-30 07:17:52 +02:00
|
|
|
import java.security.KeyManagementException;
|
|
|
|
|
import java.security.NoSuchAlgorithmException;
|
2015-05-03 15:51:21 +02:00
|
|
|
import java.util.Set;
|
|
|
|
|
|
2017-07-03 16:59:19 +02:00
|
|
|
//~--- JDK imports ------------------------------------------------------------
|
2015-04-30 07:17:52 +02:00
|
|
|
|
|
|
|
|
/**
|
2015-05-03 15:51:21 +02:00
|
|
|
* Default implementation of the {@link AdvancedHttpClient}. The default
|
2015-05-01 12:36:49 +02:00
|
|
|
* implementation uses {@link HttpURLConnection}.
|
2015-04-30 07:17:52 +02:00
|
|
|
*
|
|
|
|
|
* @author Sebastian Sdorra
|
|
|
|
|
* @since 1.46
|
|
|
|
|
*/
|
|
|
|
|
public class DefaultAdvancedHttpClient extends AdvancedHttpClient
|
|
|
|
|
{
|
|
|
|
|
|
2015-05-01 12:36:49 +02:00
|
|
|
/** proxy authorization header */
|
|
|
|
|
@VisibleForTesting
|
|
|
|
|
static final String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization";
|
2015-04-30 07:17:52 +02:00
|
|
|
|
2015-05-01 12:36:49 +02:00
|
|
|
/** connection timeout */
|
|
|
|
|
@VisibleForTesting
|
|
|
|
|
static final int TIMEOUT_CONNECTION = 30000;
|
2015-04-30 07:17:52 +02:00
|
|
|
|
2015-05-01 12:36:49 +02:00
|
|
|
/** read timeout */
|
|
|
|
|
@VisibleForTesting
|
|
|
|
|
static final int TIMEOUT_RAED = 1200000;
|
2015-04-30 07:17:52 +02:00
|
|
|
|
2015-05-03 15:51:21 +02:00
|
|
|
/** credential separator */
|
|
|
|
|
private static final String CREDENTIAL_SEPARATOR = ":";
|
|
|
|
|
|
|
|
|
|
/** basic authentication prefix */
|
|
|
|
|
private static final String PREFIX_BASIC_AUTHENTICATION = "Basic ";
|
|
|
|
|
|
2015-04-30 07:17:52 +02:00
|
|
|
/**
|
|
|
|
|
* the logger for DefaultAdvancedHttpClient
|
|
|
|
|
*/
|
|
|
|
|
private static final Logger logger =
|
|
|
|
|
LoggerFactory.getLogger(DefaultAdvancedHttpClient.class);
|
|
|
|
|
|
|
|
|
|
//~--- constructors ---------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
/**
|
2015-05-01 12:36:49 +02:00
|
|
|
* Constructs a new {@link DefaultAdvancedHttpClient}.
|
2015-04-30 07:17:52 +02:00
|
|
|
*
|
|
|
|
|
*
|
2015-05-01 12:36:49 +02:00
|
|
|
* @param configuration scm-manager main configuration
|
2015-05-03 15:51:21 +02:00
|
|
|
* @param contentTransformers content transformer
|
2016-03-17 09:42:34 +01:00
|
|
|
* @param sslContextProvider ssl context provider
|
2015-04-30 07:17:52 +02:00
|
|
|
*/
|
|
|
|
|
@Inject
|
2015-05-03 15:51:21 +02:00
|
|
|
public DefaultAdvancedHttpClient(ScmConfiguration configuration,
|
2020-10-26 16:54:05 +01:00
|
|
|
Tracer tracer, Set<ContentTransformer> contentTransformers, Provider<SSLContext> sslContextProvider)
|
2015-04-30 07:17:52 +02:00
|
|
|
{
|
|
|
|
|
this.configuration = configuration;
|
2020-10-26 16:54:05 +01:00
|
|
|
this.tracer = tracer;
|
2015-05-03 15:51:21 +02:00
|
|
|
this.contentTransformers = contentTransformers;
|
2016-03-17 09:42:34 +01:00
|
|
|
this.sslContextProvider = sslContextProvider;
|
2015-04-30 07:17:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//~--- methods --------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
/**
|
2015-05-03 15:51:21 +02:00
|
|
|
* Creates a new {@link HttpURLConnection} from the given {@link URL}. The
|
2015-05-01 12:36:49 +02:00
|
|
|
* method is visible for testing.
|
2015-04-30 07:17:52 +02:00
|
|
|
*
|
|
|
|
|
*
|
2015-05-01 12:36:49 +02:00
|
|
|
* @param url url
|
2015-04-30 07:17:52 +02:00
|
|
|
*
|
2015-05-01 12:36:49 +02:00
|
|
|
* @return new {@link HttpURLConnection}
|
|
|
|
|
*
|
|
|
|
|
* @throws IOException
|
|
|
|
|
*/
|
|
|
|
|
@VisibleForTesting
|
|
|
|
|
protected HttpURLConnection createConnection(URL url) throws IOException
|
|
|
|
|
{
|
|
|
|
|
return (HttpURLConnection) url.openConnection();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a new proxy {@link HttpURLConnection} from the given {@link URL}
|
|
|
|
|
* and {@link SocketAddress}. The method is visible for testing.
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* @param url url
|
|
|
|
|
* @param address proxy socket address
|
|
|
|
|
*
|
|
|
|
|
* @return new proxy {@link HttpURLConnection}
|
|
|
|
|
*
|
|
|
|
|
* @throws IOException
|
|
|
|
|
*/
|
|
|
|
|
@VisibleForTesting
|
|
|
|
|
protected HttpURLConnection createProxyConnecton(URL url,
|
|
|
|
|
SocketAddress address)
|
|
|
|
|
throws IOException
|
|
|
|
|
{
|
|
|
|
|
return (HttpURLConnection) url.openConnection(new Proxy(Proxy.Type.HTTP,
|
|
|
|
|
address));
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-03 15:51:21 +02:00
|
|
|
/**
|
|
|
|
|
* {@inheritDoc}
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
protected ContentTransformer createTransformer(Class<?> type, String contentType)
|
|
|
|
|
{
|
|
|
|
|
ContentTransformer responsible = null;
|
|
|
|
|
|
|
|
|
|
for (ContentTransformer transformer : contentTransformers)
|
|
|
|
|
{
|
|
|
|
|
if (transformer.isResponsible(type, contentType))
|
|
|
|
|
{
|
|
|
|
|
responsible = transformer;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (responsible == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ContentTransformerNotFoundException(
|
|
|
|
|
"could not find content transformer for content type ".concat(
|
|
|
|
|
contentType));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return responsible;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-01 12:36:49 +02:00
|
|
|
/**
|
|
|
|
|
* Executes the given request and returns the server response.
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* @param request http request
|
|
|
|
|
*
|
|
|
|
|
* @return server response
|
2015-04-30 07:17:52 +02:00
|
|
|
*
|
|
|
|
|
* @throws IOException
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
2020-10-26 16:54:05 +01:00
|
|
|
protected AdvancedHttpResponse request(BaseHttpRequest<?> request) throws IOException {
|
|
|
|
|
try (Span span = tracer.span(request.getSpanKind())) {
|
|
|
|
|
span.label("url", request.getUrl());
|
|
|
|
|
span.label("method", request.getMethod());
|
|
|
|
|
DefaultAdvancedHttpResponse response = doRequest(request);
|
|
|
|
|
span.label("status", response.getStatus());
|
|
|
|
|
if (!response.isSuccessful()) {
|
|
|
|
|
span.failed();
|
|
|
|
|
}
|
|
|
|
|
return response;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
|
private DefaultAdvancedHttpResponse doRequest(BaseHttpRequest<?> request) throws IOException {
|
|
|
|
|
HttpURLConnection connection = openConnection(request, new URL(request.getUrl()));
|
2015-04-30 07:17:52 +02:00
|
|
|
|
|
|
|
|
applyBaseSettings(request, connection);
|
|
|
|
|
|
2020-10-26 16:54:05 +01:00
|
|
|
if (connection instanceof HttpsURLConnection) {
|
2015-04-30 07:17:52 +02:00
|
|
|
applySSLSettings(request, (HttpsURLConnection) connection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Content content = null;
|
|
|
|
|
|
2020-10-26 16:54:05 +01:00
|
|
|
if (request instanceof AdvancedHttpRequestWithBody) {
|
2015-04-30 07:17:52 +02:00
|
|
|
AdvancedHttpRequestWithBody ahrwb = (AdvancedHttpRequestWithBody) request;
|
|
|
|
|
|
|
|
|
|
content = ahrwb.getContent();
|
|
|
|
|
|
2020-10-26 16:54:05 +01:00
|
|
|
if (content != null) {
|
2015-04-30 07:17:52 +02:00
|
|
|
content.prepare(ahrwb);
|
2020-10-26 16:54:05 +01:00
|
|
|
} else {
|
2015-04-30 07:17:52 +02:00
|
|
|
request.header(HttpUtil.HEADER_CONTENT_LENGTH, "0");
|
|
|
|
|
}
|
2020-10-26 16:54:05 +01:00
|
|
|
} else {
|
2015-04-30 07:17:52 +02:00
|
|
|
request.header(HttpUtil.HEADER_CONTENT_LENGTH, "0");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
applyHeaders(request, connection);
|
|
|
|
|
|
2020-10-26 16:54:05 +01:00
|
|
|
if (content != null) {
|
2015-04-30 07:17:52 +02:00
|
|
|
applyContent(connection, content);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-03 15:51:21 +02:00
|
|
|
return new DefaultAdvancedHttpResponse(this, connection,
|
2015-04-30 07:17:52 +02:00
|
|
|
connection.getResponseCode(), connection.getResponseMessage());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendProxyAuthentication(HttpURLConnection connection)
|
|
|
|
|
{
|
|
|
|
|
String username = configuration.getProxyUser();
|
|
|
|
|
String password = configuration.getProxyPassword();
|
|
|
|
|
|
|
|
|
|
if (!Strings.isNullOrEmpty(username) ||!Strings.isNullOrEmpty(password))
|
|
|
|
|
{
|
|
|
|
|
logger.debug("append proxy authentication header for user {}", username);
|
|
|
|
|
|
|
|
|
|
String auth = username.concat(CREDENTIAL_SEPARATOR).concat(password);
|
|
|
|
|
|
|
|
|
|
auth = Base64.encodeToString(auth.getBytes());
|
|
|
|
|
connection.addRequestProperty(HEADER_PROXY_AUTHORIZATION,
|
|
|
|
|
PREFIX_BASIC_AUTHENTICATION.concat(auth));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void applyBaseSettings(BaseHttpRequest<?> request,
|
|
|
|
|
HttpURLConnection connection)
|
|
|
|
|
throws ProtocolException
|
|
|
|
|
{
|
|
|
|
|
connection.setRequestMethod(request.getMethod());
|
|
|
|
|
connection.setReadTimeout(TIMEOUT_RAED);
|
|
|
|
|
connection.setConnectTimeout(TIMEOUT_CONNECTION);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void applyContent(HttpURLConnection connection, Content content)
|
|
|
|
|
throws IOException
|
|
|
|
|
{
|
|
|
|
|
connection.setDoOutput(true);
|
|
|
|
|
|
|
|
|
|
OutputStream output = null;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
output = connection.getOutputStream();
|
|
|
|
|
content.process(output);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
Closeables.close(output, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void applyHeaders(BaseHttpRequest<?> request,
|
|
|
|
|
HttpURLConnection connection)
|
|
|
|
|
{
|
|
|
|
|
Multimap<String, String> headers = request.getHeaders();
|
|
|
|
|
|
|
|
|
|
for (String key : headers.keySet())
|
|
|
|
|
{
|
|
|
|
|
for (String value : headers.get(key))
|
|
|
|
|
{
|
2015-05-01 12:36:49 +02:00
|
|
|
connection.addRequestProperty(key, value);
|
2015-04-30 07:17:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void applySSLSettings(BaseHttpRequest<?> request,
|
|
|
|
|
HttpsURLConnection connection)
|
|
|
|
|
{
|
|
|
|
|
if (request.isDisableCertificateValidation())
|
|
|
|
|
{
|
|
|
|
|
logger.trace("disable certificate validation");
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
TrustManager[] trustAllCerts = new TrustManager[] {
|
|
|
|
|
new TrustAllTrustManager() };
|
2020-10-26 16:55:22 +01:00
|
|
|
SSLContext sc = SSLContext.getInstance("TLS");
|
2015-04-30 07:17:52 +02:00
|
|
|
|
|
|
|
|
sc.init(null, trustAllCerts, new java.security.SecureRandom());
|
|
|
|
|
connection.setSSLSocketFactory(sc.getSocketFactory());
|
|
|
|
|
}
|
2017-07-03 16:59:19 +02:00
|
|
|
catch (KeyManagementException | NoSuchAlgorithmException ex)
|
2015-04-30 07:17:52 +02:00
|
|
|
{
|
|
|
|
|
logger.error("could not disable certificate validation", ex);
|
|
|
|
|
}
|
2020-10-26 16:54:05 +01:00
|
|
|
}
|
|
|
|
|
else
|
2016-03-17 09:42:34 +01:00
|
|
|
{
|
|
|
|
|
logger.trace("set ssl socker factory from provider");
|
|
|
|
|
connection.setSSLSocketFactory(sslContextProvider.get().getSocketFactory());
|
2015-04-30 07:17:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (request.isDisableHostnameValidation())
|
|
|
|
|
{
|
|
|
|
|
logger.trace("disable hostname validation");
|
|
|
|
|
connection.setHostnameVerifier(new TrustAllHostnameVerifier());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private HttpURLConnection openConnection(BaseHttpRequest<?> request, URL url)
|
|
|
|
|
throws IOException
|
|
|
|
|
{
|
|
|
|
|
HttpURLConnection connection;
|
|
|
|
|
|
|
|
|
|
if (isProxyEnabled(request))
|
|
|
|
|
{
|
2020-10-26 16:55:22 +01:00
|
|
|
connection = openProxyConnection(url);
|
2015-04-30 07:17:52 +02:00
|
|
|
appendProxyAuthentication(connection);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (request.isIgnoreProxySettings())
|
|
|
|
|
{
|
|
|
|
|
logger.trace("ignore proxy settings");
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-26 16:55:22 +01:00
|
|
|
if (logger.isDebugEnabled()) {
|
|
|
|
|
logger.debug("fetch {}", url.toExternalForm());
|
|
|
|
|
}
|
2015-04-30 07:17:52 +02:00
|
|
|
|
2015-05-01 12:36:49 +02:00
|
|
|
connection = createConnection(url);
|
2015-04-30 07:17:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return connection;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-26 16:55:22 +01:00
|
|
|
private HttpURLConnection openProxyConnection(URL url)
|
2015-04-30 07:17:52 +02:00
|
|
|
throws IOException
|
|
|
|
|
{
|
|
|
|
|
if (logger.isDebugEnabled())
|
|
|
|
|
{
|
|
|
|
|
logger.debug("fetch '{}' using proxy {}:{}", url.toExternalForm(),
|
|
|
|
|
configuration.getProxyServer(), configuration.getProxyPort());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SocketAddress address =
|
|
|
|
|
new InetSocketAddress(configuration.getProxyServer(),
|
|
|
|
|
configuration.getProxyPort());
|
|
|
|
|
|
2015-05-01 12:36:49 +02:00
|
|
|
return createProxyConnecton(url, address);
|
2015-04-30 07:17:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//~--- get methods ----------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
private boolean isProxyEnabled(BaseHttpRequest<?> request)
|
|
|
|
|
{
|
|
|
|
|
return !request.isIgnoreProxySettings()
|
|
|
|
|
&& Proxies.isEnabled(configuration, request.getUrl());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//~--- fields ---------------------------------------------------------------
|
|
|
|
|
|
2015-05-01 12:36:49 +02:00
|
|
|
/** scm-manager main configuration */
|
2015-04-30 07:17:52 +02:00
|
|
|
private final ScmConfiguration configuration;
|
2015-05-03 15:51:21 +02:00
|
|
|
|
|
|
|
|
/** set of content transformers */
|
|
|
|
|
private final Set<ContentTransformer> contentTransformers;
|
2020-10-26 16:54:05 +01:00
|
|
|
|
2016-03-17 09:42:34 +01:00
|
|
|
/** ssl context provider */
|
|
|
|
|
private final Provider<SSLContext> sslContextProvider;
|
2020-10-26 16:54:05 +01:00
|
|
|
|
|
|
|
|
/** tracer used for request tracing */
|
2020-10-26 16:55:22 +01:00
|
|
|
private final Tracer tracer;
|
2015-04-30 07:17:52 +02:00
|
|
|
}
|