diff --git a/asciidoc/use/settings.adoc b/asciidoc/use/settings.adoc
index 4c98e075..319abe1a 100644
--- a/asciidoc/use/settings.adoc
+++ b/asciidoc/use/settings.adoc
@@ -55,7 +55,7 @@ my.variable.yourid=your.value
== HTTP Authentication
-__Note__: HTTP Authentication can be used only if link:http://jakarta.apache.org/commons/httpclient/[commons-httpclient.jar] is in your classpath
+__Note__: HTTP Authentication can be used only if link:https://hc.apache.org/httpcomponents-client-ga/index.html[HttpComponents HttpClient library] (minimum of 4.5.3 version) and its linke:https://hc.apache.org/httpcomponents-client-4.5.x/dependency-management.html[dependencies] are in your classpath.
If any of the url you use in Ivy (especially in dependency resolvers) need HTTP authentication, then you have to provide the `host`, `realm`, `username` and `passwd` attributes of the configure task. These settings will then be used in any further call to ivy tasks.
diff --git a/ivy.xml b/ivy.xml
index d7417c89..f38c1e5d 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -44,7 +44,7 @@
-
+
@@ -62,8 +62,10 @@
-
+
+
+
diff --git a/src/java/org/apache/ivy/util/url/AbstractURLHandler.java b/src/java/org/apache/ivy/util/url/AbstractURLHandler.java
index a1184d2c..c951ff17 100644
--- a/src/java/org/apache/ivy/util/url/AbstractURLHandler.java
+++ b/src/java/org/apache/ivy/util/url/AbstractURLHandler.java
@@ -86,11 +86,7 @@ public abstract class AbstractURLHandler implements URLHandler {
}
protected String getUserAgent() {
- String userAgent = System.getProperty("http.agent");
- if (userAgent == null) {
- userAgent = "Apache Ivy/" + Ivy.getIvyVersion();
- }
- return userAgent;
+ return System.getProperty("http.agent", "Apache Ivy/" + Ivy.getIvyVersion());
}
protected void validatePutStatusCode(URL dest, int statusCode, String statusMessage)
diff --git a/src/java/org/apache/ivy/util/url/HttpClientHandler.java b/src/java/org/apache/ivy/util/url/HttpClientHandler.java
index 469ad0c4..04d0008b 100644
--- a/src/java/org/apache/ivy/util/url/HttpClientHandler.java
+++ b/src/java/org/apache/ivy/util/url/HttpClientHandler.java
@@ -17,24 +17,34 @@
*/
package org.apache.ivy.util.url;
-import org.apache.commons.httpclient.Credentials;
-import org.apache.commons.httpclient.Header;
-import org.apache.commons.httpclient.HttpClient;
-import org.apache.commons.httpclient.HttpException;
-import org.apache.commons.httpclient.HttpMethodBase;
-import org.apache.commons.httpclient.HttpStatus;
-import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
-import org.apache.commons.httpclient.NTCredentials;
-import org.apache.commons.httpclient.auth.AuthPolicy;
-import org.apache.commons.httpclient.auth.AuthScheme;
-import org.apache.commons.httpclient.auth.AuthScope;
-import org.apache.commons.httpclient.auth.CredentialsNotAvailableException;
-import org.apache.commons.httpclient.auth.CredentialsProvider;
-import org.apache.commons.httpclient.methods.GetMethod;
-import org.apache.commons.httpclient.methods.HeadMethod;
-import org.apache.commons.httpclient.methods.PutMethod;
-import org.apache.commons.httpclient.methods.RequestEntity;
-import org.apache.commons.httpclient.params.HttpMethodParams;
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.auth.AuthSchemeProvider;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.NTCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.config.AuthSchemes;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.config.Lookup;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.conn.routing.HttpRoutePlanner;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.FileEntity;
+import org.apache.http.impl.auth.BasicSchemeFactory;
+import org.apache.http.impl.auth.DigestSchemeFactory;
+import org.apache.http.impl.auth.NTLMSchemeFactory;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.impl.conn.SystemDefaultRoutePlanner;
import org.apache.ivy.core.settings.TimeoutConstraint;
import org.apache.ivy.util.CopyProgressListener;
import org.apache.ivy.util.FileUtil;
@@ -42,56 +52,81 @@ import org.apache.ivy.util.HostUtil;
import org.apache.ivy.util.Message;
import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
+import java.net.ProxySelector;
import java.net.URL;
-import java.net.UnknownHostException;
+import java.nio.charset.Charset;
import java.text.ParseException;
import java.text.SimpleDateFormat;
-import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Locale;
+import java.util.concurrent.ConcurrentHashMap;
/**
*
*/
-public class HttpClientHandler extends AbstractURLHandler {
+class HttpClientHandler extends AbstractURLHandler implements AutoCloseable {
private static final SimpleDateFormat LAST_MODIFIED_FORMAT = new SimpleDateFormat(
"EEE, d MMM yyyy HH:mm:ss z", Locale.US);
- // proxy configuration: obtain from system properties
- private int proxyPort;
+ // A instance of the HttpClientHandler which gets registered to be closed
+ // when the JVM exits
+ static final HttpClientHandler DELETE_ON_EXIT_INSTANCE;
- private String proxyHost = null;
-
- private String proxyUserName = null;
-
- private String proxyPasswd = null;
-
- private HttpClientHelper httpClientHelper;
-
- private static HttpClient httpClient;
-
- public HttpClientHandler() {
- configureProxy();
+ static {
+ DELETE_ON_EXIT_INSTANCE = new HttpClientHandler();
+ final Thread shutdownHook = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ DELETE_ON_EXIT_INSTANCE.close();
+ } catch (Exception e) {
+ // ignore since this is anyway happening during shutdown of the JVM
+ }
+ }
+ });
+ shutdownHook.setName("ivy-httpclient-shutdown-handler");
+ shutdownHook.setDaemon(true);
+ Runtime.getRuntime().addShutdownHook(shutdownHook);
}
- private void configureProxy() {
- proxyHost = System.getProperty("http.proxyHost");
- // TODO constant is better ...
- if (useProxy()) {
- proxyPort = Integer.parseInt(System.getProperty("http.proxyPort", "80"));
- proxyUserName = System.getProperty("http.proxyUser");
- proxyPasswd = System.getProperty("http.proxyPassword");
- // It seems there is no equivalent in HttpClient for
- // 'http.nonProxyHosts' property
- Message.verbose("proxy configured: host=" + proxyHost + " port=" + proxyPort + " user="
- + proxyUserName);
- } else {
- Message.verbose("no proxy configured");
- }
+ private final CloseableHttpClient httpClient;
+
+ HttpClientHandler() {
+ this.httpClient = buildUnderlyingClient();
+ }
+
+ private CloseableHttpClient buildUnderlyingClient() {
+ return HttpClients.custom()
+ .setConnectionManager(createConnectionManager())
+ .setRoutePlanner(createProxyRoutePlanner())
+ .setUserAgent(this.getUserAgent())
+ .setDefaultAuthSchemeRegistry(createAuthSchemeRegistry())
+ .setDefaultCredentialsProvider(new IvyCredentialsProvider())
+ .build();
+ }
+
+ private static HttpRoutePlanner createProxyRoutePlanner() {
+ // use the standard JRE ProxySelector to get proxy information
+ Message.verbose("Using JRE standard ProxySelector for configuring HTTP proxy");
+ return new SystemDefaultRoutePlanner(ProxySelector.getDefault());
+ }
+
+ private static Lookup createAuthSchemeRegistry() {
+ return RegistryBuilder.create().register(AuthSchemes.DIGEST, new DigestSchemeFactory())
+ .register(AuthSchemes.BASIC, new BasicSchemeFactory())
+ .register(AuthSchemes.NTLM, new NTLMSchemeFactory())
+ .build();
+ }
+
+ private static HttpClientConnectionManager createConnectionManager() {
+ return new PoolingHttpClientConnectionManager();
+ }
+
+ private static List getAuthSchemePreferredOrder() {
+ return Arrays.asList(AuthSchemes.DIGEST, AuthSchemes.BASIC, AuthSchemes.NTLM);
}
@Override
@@ -103,16 +138,10 @@ public class HttpClientHandler extends AbstractURLHandler {
public InputStream openStream(final URL url, final TimeoutConstraint timeoutConstraint) throws IOException {
final int connectionTimeout = (timeoutConstraint == null || timeoutConstraint.getConnectionTimeout() < 0) ? 0 : timeoutConstraint.getConnectionTimeout();
final int readTimeout = (timeoutConstraint == null || timeoutConstraint.getReadTimeout() < 0) ? 0 : timeoutConstraint.getReadTimeout();
- final GetMethod get = doGet(url, connectionTimeout, readTimeout);
- if (!checkStatusCode(url, get)) {
- get.releaseConnection();
- throw new IOException("The HTTP response code for " + url
- + " did not indicate a success." + " See log for more detail.");
- }
-
- Header encoding = get.getResponseHeader("Content-Encoding");
- return getDecodingInputStream(encoding == null ? null : encoding.getValue(),
- get.getResponseBodyAsStream());
+ final CloseableHttpResponse response = doGet(url, connectionTimeout, readTimeout);
+ this.requireSuccessStatus(HttpGet.METHOD_NAME, url, response);
+ final Header encoding = this.getContentEncoding(response);
+ return getDecodingInputStream(encoding == null ? null : encoding.getValue(), response.getEntity().getContent());
}
@Override
@@ -126,21 +155,15 @@ public class HttpClientHandler extends AbstractURLHandler {
final int connectionTimeout = (timeoutConstraint == null || timeoutConstraint.getConnectionTimeout() < 0) ? 0 : timeoutConstraint.getConnectionTimeout();
final int readTimeout = (timeoutConstraint == null || timeoutConstraint.getReadTimeout() < 0) ? 0 : timeoutConstraint.getReadTimeout();
- final GetMethod get = doGet(src, connectionTimeout, readTimeout);
- try {
+ try (final CloseableHttpResponse response = doGet(src, connectionTimeout, readTimeout)) {
// We can only figure the content we got is want we want if the status is success.
- if (!checkStatusCode(src, get)) {
- throw new IOException("The HTTP response code for " + src
- + " did not indicate a success." + " See log for more detail.");
+ this.requireSuccessStatus(HttpGet.METHOD_NAME, src, response);
+ final Header encoding = this.getContentEncoding(response);
+ try (final InputStream is = getDecodingInputStream(encoding == null ? null : encoding.getValue(),
+ response.getEntity().getContent())) {
+ FileUtil.copy(is, dest, listener);
}
-
- Header encoding = get.getResponseHeader("Content-Encoding");
- InputStream is = getDecodingInputStream(encoding == null ? null : encoding.getValue(),
- get.getResponseBodyAsStream());
- FileUtil.copy(is, dest, listener);
- dest.setLastModified(getLastModified(get));
- } finally {
- get.releaseConnection();
+ dest.setLastModified(getLastModified(response));
}
}
@@ -152,22 +175,20 @@ public class HttpClientHandler extends AbstractURLHandler {
@Override
public void upload(final File src, final URL dest, final CopyProgressListener listener, final TimeoutConstraint timeoutConstraint) throws IOException {
final int connectionTimeout = (timeoutConstraint == null || timeoutConstraint.getConnectionTimeout() < 0) ? 0 : timeoutConstraint.getConnectionTimeout();
- final HttpClient client = getClient();
- // TODO: Use the newer way of settings the connection timeout via HttpConnectionManagerParams
- // once we stop support for HttpClient 2.x version
- client.setConnectionTimeout(connectionTimeout);
-
- final PutMethod put = new PutMethod(normalizeToString(dest));
- put.setDoAuthentication(useAuthentication(dest) || useProxyAuthentication());
- put.getParams().setBooleanParameter("http.protocol.expect-continue", true);
- try {
- put.setRequestEntity(new FileRequestEntity(src));
- int statusCode = client.executeMethod(put);
- validatePutStatusCode(dest, statusCode, null);
- } finally {
- put.releaseConnection();
+ final int readTimeout = (timeoutConstraint == null || timeoutConstraint.getReadTimeout() < 0) ? 0 : timeoutConstraint.getReadTimeout();
+ final RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeout)
+ .setConnectTimeout(connectionTimeout)
+ .setAuthenticationEnabled(hasCredentialsConfigured(dest))
+ .setTargetPreferredAuthSchemes(getAuthSchemePreferredOrder())
+ .setProxyPreferredAuthSchemes(getAuthSchemePreferredOrder())
+ .setExpectContinueEnabled(true)
+ .build();
+ final HttpPut put = new HttpPut(normalizeToString(dest));
+ put.setConfig(requestConfig);
+ put.setEntity(new FileEntity(src));
+ try (final CloseableHttpResponse response = this.httpClient.execute(put)) {
+ validatePutStatusCode(dest, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase());
}
-
}
@Override
@@ -184,289 +205,182 @@ public class HttpClientHandler extends AbstractURLHandler {
public URLInfo getURLInfo(final URL url, final TimeoutConstraint timeoutConstraint) {
final int connectionTimeout = (timeoutConstraint == null || timeoutConstraint.getConnectionTimeout() < 0) ? 0 : timeoutConstraint.getConnectionTimeout();
final int readTimeout = (timeoutConstraint == null || timeoutConstraint.getReadTimeout() < 0) ? 0 : timeoutConstraint.getReadTimeout();
- HttpMethodBase method = null;
+ CloseableHttpResponse response = null;
try {
+ final String httpMethod;
if (getRequestMethod() == URLHandler.REQUEST_METHOD_HEAD) {
- method = doHead(url, connectionTimeout, readTimeout);
+ httpMethod = HttpHead.METHOD_NAME;
+ response = doHead(url, connectionTimeout, readTimeout);
} else {
- method = doGet(url, connectionTimeout, readTimeout);
+ httpMethod = HttpGet.METHOD_NAME;
+ response = doGet(url, connectionTimeout, readTimeout);
}
- if (checkStatusCode(url, method)) {
- return new URLInfo(true, getResponseContentLength(method), getLastModified(method),
- method.getRequestCharSet());
+ if (checkStatusCode(httpMethod, url, response)) {
+ final HttpEntity responseEntity = response.getEntity();
+ final Charset charSet = ContentType.getOrDefault(responseEntity).getCharset();
+ return new URLInfo(true, responseEntity == null ? 0 : responseEntity.getContentLength(),
+ getLastModified(response), charSet.name());
}
- } catch (HttpException e) {
- Message.error("HttpClientHandler: " + e.getMessage() + ":" + e.getReasonCode() + "="
- + e.getReason() + " url=" + url);
- } catch (UnknownHostException e) {
- Message.warn("Host " + e.getMessage() + " not found. url=" + url);
- Message.info("You probably access the destination server through "
- + "a proxy server that is not well configured.");
- } catch (IOException e) {
+ } catch (IOException | IllegalArgumentException e) {
+ // IllegalArgumentException is thrown by HttpClient library to indicate the URL is not valid,
+ // this happens for instance when trying to download a dynamic version (cfr IVY-390)
Message.error("HttpClientHandler: " + e.getMessage() + " url=" + url);
- } catch (IllegalArgumentException e) {
- // thrown by HttpClient to indicate the URL is not valid, this happens for instance
- // when trying to download a dynamic version (cfr IVY-390)
} finally {
- if (method != null) {
- method.releaseConnection();
+ if (response != null) {
+ try {
+ response.close();
+ } catch (IOException e) {
+ // ignore
+ }
}
}
return UNAVAILABLE;
}
- private boolean checkStatusCode(URL url, HttpMethodBase method) {
- int status = method.getStatusCode();
+ private boolean checkStatusCode(final String httpMethod, final URL sourceURL, final HttpResponse response) {
+ final int status = response.getStatusLine().getStatusCode();
if (status == HttpStatus.SC_OK) {
return true;
}
-
// IVY-1328: some servers return a 204 on a HEAD request
- if ("HEAD".equals(method.getName()) && (status == 204)) {
+ if (HttpHead.METHOD_NAME.equals(httpMethod) && (status == 204)) {
return true;
}
- Message.debug("HTTP response status: " + status + " url=" + url);
+ Message.debug("HTTP response status: " + status + " url=" + sourceURL);
if (status == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) {
Message.warn("Your proxy requires authentication.");
} else if (String.valueOf(status).startsWith("4")) {
- Message.verbose("CLIENT ERROR: " + method.getStatusText() + " url=" + url);
+ Message.verbose("CLIENT ERROR: " + response.getStatusLine().getReasonPhrase() + " url=" + sourceURL);
} else if (String.valueOf(status).startsWith("5")) {
- Message.error("SERVER ERROR: " + method.getStatusText() + " url=" + url);
+ Message.error("SERVER ERROR: " + response.getStatusLine().getReasonPhrase() + " url=" + sourceURL);
}
-
return false;
}
- private long getLastModified(HttpMethodBase method) {
- Header header = method.getResponseHeader("last-modified");
- if (header != null) {
- String lastModified = header.getValue();
- try {
- return LAST_MODIFIED_FORMAT.parse(lastModified).getTime();
- } catch (ParseException e) {
- // ignored
- }
- return System.currentTimeMillis();
- } else {
+ /**
+ * Checks the status code of the response and if it's considered as successful response, then this method just returns back.
+ * Else it {@link CloseableHttpResponse#close() closes the response} and throws an {@link IOException} for the unsuccessful response.
+ *
+ * @param httpMethod The HTTP method that was used for the source request
+ * @param sourceURL The URL of the source request
+ * @param response The response to the source request
+ * @throws IOException Thrown if the response was considered unsuccessful
+ */
+ private void requireSuccessStatus(final String httpMethod, final URL sourceURL, final CloseableHttpResponse response) throws IOException {
+ if (this.checkStatusCode(httpMethod, sourceURL, response)) {
+ return;
+ }
+ // this is now considered an unsuccessful response, so close the response and throw an exception
+ try {
+ response.close();
+ } catch (Exception e) {
+ // log and move on
+ Message.debug("Could not close the HTTP response for url=" + sourceURL, e);
+ }
+ throw new IOException("Response to request '" + httpMethod + " " + sourceURL + "' did not indicate a success (see debug log for details)");
+ }
+
+ private Header getContentEncoding(final HttpResponse response) {
+ return response.getFirstHeader("Content-Encoding");
+ }
+
+ private long getLastModified(final HttpResponse response) {
+ final Header header = response.getFirstHeader("last-modified");
+ if (header == null) {
return System.currentTimeMillis();
}
- }
-
- private long getResponseContentLength(HttpMethodBase head) {
- return getHttpClientHelper().getResponseContentLength(head);
- }
-
- private HttpClientHelper getHttpClientHelper() {
- if (httpClientHelper == null) {
- // use commons httpclient 3.0 if available
- try {
- HttpMethodBase.class.getMethod("getResponseContentLength");
- httpClientHelper = new HttpClientHelper3x();
- Message.verbose("using commons httpclient 3.x helper");
- } catch (SecurityException e) {
- Message.verbose("unable to get access to getResponseContentLength of "
- + "commons-httpclient HeadMethod. Please use commons-httpclient 3.0 or "
- + "use ivy with sufficient security permissions.");
- Message.verbose("exception: " + e.getMessage());
- httpClientHelper = new HttpClientHelper2x();
- Message.verbose("using commons httpclient 2.x helper");
- } catch (NoSuchMethodException e) {
- httpClientHelper = new HttpClientHelper2x();
- Message.verbose("using commons httpclient 2.x helper");
- }
+ final String lastModified = header.getValue();
+ try {
+ return LAST_MODIFIED_FORMAT.parse(lastModified).getTime();
+ } catch (ParseException e) {
+ // ignored
}
- return httpClientHelper;
+ return System.currentTimeMillis();
}
- public int getHttpClientMajorVersion() {
- HttpClientHelper helper = getHttpClientHelper();
- return helper.getHttpClientMajorVersion();
+ private CloseableHttpResponse doGet(final URL url, final int connectionTimeout, final int readTimeout) throws IOException {
+ final RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeout)
+ .setConnectTimeout(connectionTimeout)
+ .setAuthenticationEnabled(hasCredentialsConfigured(url))
+ .setTargetPreferredAuthSchemes(getAuthSchemePreferredOrder())
+ .setProxyPreferredAuthSchemes(getAuthSchemePreferredOrder())
+ .build();
+ final HttpGet httpGet = new HttpGet(normalizeToString(url));
+ httpGet.setConfig(requestConfig);
+ httpGet.addHeader("Accept-Encoding", "gzip,deflate");
+ return this.httpClient.execute(httpGet);
}
- private GetMethod doGet(final URL url, final int connectionTimeout, final int readTimeout) throws IOException {
- final HttpClient client = getClient();
- // TODO: Use the newer way of settings the connection and read timeout via HttpConnectionManagerParams
- // once we stop support for HttpClient 2.x version
- client.setConnectionTimeout(connectionTimeout);
- client.setTimeout(readTimeout);
-
- GetMethod get = new GetMethod(normalizeToString(url));
- get.setDoAuthentication(useAuthentication(url) || useProxyAuthentication());
- get.setRequestHeader("Accept-Encoding", "gzip,deflate");
- client.executeMethod(get);
- return get;
+ private CloseableHttpResponse doHead(final URL url, final int connectionTimeout, final int readTimeout) throws IOException {
+ final RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeout)
+ .setConnectTimeout(connectionTimeout)
+ .setAuthenticationEnabled(hasCredentialsConfigured(url))
+ .setTargetPreferredAuthSchemes(getAuthSchemePreferredOrder())
+ .setProxyPreferredAuthSchemes(getAuthSchemePreferredOrder())
+ .build();
+ final HttpHead httpHead = new HttpHead(normalizeToString(url));
+ httpHead.setConfig(requestConfig);
+ return this.httpClient.execute(httpHead);
}
- private HeadMethod doHead(final URL url, final int connectionTimeout, final int readTimeout) throws IOException {
- final HttpClient client = getClient();
- // TODO: Use the newer way of settings the connection and read timeout via HttpConnectionManagerParams
- // once we stop support for HttpClient 2.x version
- client.setConnectionTimeout(connectionTimeout);
- client.setTimeout(readTimeout);
-
- HeadMethod head = new HeadMethod(normalizeToString(url));
- head.setDoAuthentication(useAuthentication(url) || useProxyAuthentication());
- client.executeMethod(head);
- return head;
- }
-
- private HttpClient getClient() {
- if (httpClient == null) {
- final MultiThreadedHttpConnectionManager connManager = new MultiThreadedHttpConnectionManager();
- httpClient = new HttpClient(connManager);
-
- Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
- public void run() {
- connManager.shutdown();
- }
- }));
-
- List authPrefs = new ArrayList<>(3);
- authPrefs.add(AuthPolicy.DIGEST);
- authPrefs.add(AuthPolicy.BASIC);
- authPrefs.add(AuthPolicy.NTLM); // put it at the end to give less priority (IVY-213)
- httpClient.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
-
- if (useProxy()) {
- httpClient.getHostConfiguration().setProxy(proxyHost, proxyPort);
- if (useProxyAuthentication()) {
- httpClient.getState().setProxyCredentials(
- new AuthScope(proxyHost, proxyPort, AuthScope.ANY_REALM),
- createCredentials(proxyUserName, proxyPasswd));
- }
- }
-
- // user-agent
- httpClient.getParams().setParameter(HttpMethodParams.USER_AGENT,
- getUserAgent());
-
- // authentication
- httpClient.getParams().setParameter(CredentialsProvider.PROVIDER,
- new IvyCredentialsProvider());
- }
-
- return httpClient;
- }
-
- private boolean useProxy() {
- return proxyHost != null && proxyHost.trim().length() > 0;
- }
-
- private boolean useAuthentication(URL url) {
+ private boolean hasCredentialsConfigured(final URL url) {
return CredentialsStore.INSTANCE.hasCredentials(url.getHost());
}
- private boolean useProxyAuthentication() {
- return (proxyUserName != null && proxyUserName.trim().length() > 0);
- }
-
- private static final class HttpClientHelper3x implements HttpClientHelper {
- private static final int VERSION = 3;
-
- private HttpClientHelper3x() {
+ @Override
+ public void close() throws Exception {
+ if (this.httpClient != null) {
+ this.httpClient.close();
}
-
- public long getResponseContentLength(HttpMethodBase method) {
- return method.getResponseContentLength();
- }
-
- /**
- * {@inheritDoc}
- */
- public int getHttpClientMajorVersion() {
- return VERSION;
- }
- }
-
- private static final class HttpClientHelper2x implements HttpClientHelper {
- private static final int VERSION = 2;
-
- private HttpClientHelper2x() {
- }
-
- public long getResponseContentLength(HttpMethodBase method) {
- Header header = method.getResponseHeader("Content-Length");
- if (header != null) {
- try {
- return Integer.parseInt(header.getValue());
- } catch (NumberFormatException e) {
- Message.verbose("Invalid content-length value: " + e.getMessage());
- }
- }
- return 0;
- }
-
- /**
- * {@inheritDoc}
- */
- public int getHttpClientMajorVersion() {
- return VERSION;
- }
- }
-
- public interface HttpClientHelper {
- long getResponseContentLength(HttpMethodBase method);
-
- int getHttpClientMajorVersion();
}
private static class IvyCredentialsProvider implements CredentialsProvider {
- public Credentials getCredentials(AuthScheme scheme, String host, int port, boolean proxy)
- throws CredentialsNotAvailableException {
- String realm = scheme.getRealm();
+ private final ConcurrentHashMap cachedCreds = new ConcurrentHashMap<>();
- org.apache.ivy.util.Credentials c = CredentialsStore.INSTANCE.getCredentials(realm,
- host);
- if (c != null) {
- return createCredentials(c.getUserName(), c.getPasswd());
+ @Override
+ public void setCredentials(final AuthScope authscope, final Credentials credentials) {
+ if (authscope == null) {
+ throw new IllegalArgumentException("AuthScope cannot be null");
}
-
- return null;
- }
- }
-
- private static Credentials createCredentials(String username, String password) {
- String user;
- String domain;
-
- int backslashIndex = username.indexOf('\\');
- if (backslashIndex >= 0) {
- user = username.substring(backslashIndex + 1);
- domain = username.substring(0, backslashIndex);
- } else {
- user = username;
- domain = System.getProperty("http.auth.ntlm.domain", "");
+ this.cachedCreds.put(authscope, credentials);
}
- return new NTCredentials(user, password, HostUtil.getLocalHostName(), domain);
- }
-
- private static class FileRequestEntity implements RequestEntity {
- private File file;
-
- public FileRequestEntity(File file) {
- this.file = file;
- }
-
- public long getContentLength() {
- return file.length();
- }
-
- public String getContentType() {
- return null;
- }
-
- public boolean isRepeatable() {
- return true;
- }
-
- public void writeRequest(OutputStream out) throws IOException {
- try (InputStream instream = new FileInputStream(file)) {
- FileUtil.copy(instream, out, null, false);
+ @Override
+ public Credentials getCredentials(final AuthScope authscope) {
+ if (authscope == null) {
+ return null;
}
+ // TODO: check if the credentials are requested for a proxy, in which case, we use the
+ // system configured proxy (system) properties to return the creds
+
+ final String realm = authscope.getRealm();
+ final String host = authscope.getHost();
+ final org.apache.ivy.util.Credentials ivyConfiguredCred = CredentialsStore.INSTANCE.getCredentials(realm, host);
+ if (ivyConfiguredCred == null) {
+ return null;
+ }
+ return createCredentials(ivyConfiguredCred.getUserName(), ivyConfiguredCred.getPasswd());
+ }
+
+ @Override
+ public void clear() {
+ this.cachedCreds.clear();
+ }
+
+ private static Credentials createCredentials(final String username, final String password) {
+ final String user;
+ final String domain;
+ int backslashIndex = username.indexOf('\\');
+ if (backslashIndex >= 0) {
+ user = username.substring(backslashIndex + 1);
+ domain = username.substring(0, backslashIndex);
+ } else {
+ user = username;
+ domain = System.getProperty("http.auth.ntlm.domain", "");
+ }
+ return new NTCredentials(user, password, HostUtil.getLocalHostName(), domain);
}
}
-
}
diff --git a/src/java/org/apache/ivy/util/url/URLHandlerRegistry.java b/src/java/org/apache/ivy/util/url/URLHandlerRegistry.java
index 8cf62de1..629fd331 100644
--- a/src/java/org/apache/ivy/util/url/URLHandlerRegistry.java
+++ b/src/java/org/apache/ivy/util/url/URLHandlerRegistry.java
@@ -19,6 +19,8 @@ package org.apache.ivy.util.url;
import org.apache.ivy.util.Message;
+import java.lang.reflect.Field;
+
/**
*
*/
@@ -32,7 +34,7 @@ public final class URLHandlerRegistry {
return defaultHandler;
}
- public static void setDefault(URLHandler def) {
+ public static void setDefault(final URLHandler def) {
defaultHandler = def;
}
@@ -44,25 +46,17 @@ public final class URLHandlerRegistry {
*/
public static URLHandler getHttp() {
try {
- Class.forName("org.apache.commons.httpclient.HttpClient");
-
- // temporary fix for IVY-880: only use HttpClientHandler when
- // http-client-3.x is available
- Class.forName("org.apache.commons.httpclient.params.HttpClientParams");
-
- Class> handler = Class.forName("org.apache.ivy.util.url.HttpClientHandler");
- Message.verbose("jakarta commons httpclient detected: using it for http downloading");
- return (URLHandler) handler.newInstance();
- } catch (ClassNotFoundException e) {
- Message.verbose("jakarta commons httpclient not found: using jdk url handling");
- return new BasicURLHandler();
- } catch (NoClassDefFoundError e) {
- Message.verbose("error occurred while loading jakarta commons httpclient: "
- + e.getMessage());
- Message.verbose("Using jdk url handling instead.");
- return new BasicURLHandler();
- } catch (InstantiationException | IllegalAccessException e) {
- Message.verbose("couldn't instantiate HttpClientHandler: using jdk url handling");
+ // check for the presence of HttpComponents HttpClient
+ Class.forName("org.apache.http.client.HttpClient");
+ // load our (wrapper) http handler
+ final Class> handler = Class.forName("org.apache.ivy.util.url.HttpClientHandler");
+ // we always use just one instance which is internally registered to be closed
+ // when the JVM exits
+ final Field instance = handler.getDeclaredField("DELETE_ON_EXIT_INSTANCE");
+ return (URLHandler) instance.get(null);
+ } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
+ Message.verbose("Using JDK backed URL handler for HTTP interaction since the " +
+ "Apache HttpComponents HttpClient backed handler couldn't be created due to: " + e.getMessage());
return new BasicURLHandler();
}
}
diff --git a/test/java/org/apache/ivy/util/url/HttpClientURLHandlerTest.java b/test/java/org/apache/ivy/util/url/HttpClientURLHandlerTest.java
new file mode 100644
index 00000000..0690c3ec
--- /dev/null
+++ b/test/java/org/apache/ivy/util/url/HttpClientURLHandlerTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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 org.apache.ivy.util.url;
+
+import java.io.File;
+import java.net.URL;
+
+import org.apache.ivy.core.settings.NamedTimeoutConstraint;
+import org.apache.ivy.core.settings.TimeoutConstraint;
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.url.URLHandler.URLInfo;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test {@link HttpClientHandler}
+ */
+public class HttpclientURLHandlerTest {
+ // remote.test
+ private File testDir;
+
+ private HttpClientHandler handler;
+ private final TimeoutConstraint defaultTimeoutConstraint;
+
+ {
+ defaultTimeoutConstraint = new NamedTimeoutConstraint("default-http-client-handler-timeout");
+ ((NamedTimeoutConstraint) defaultTimeoutConstraint).setConnectionTimeout(5000);
+ }
+
+ @Before
+ public void setUp() {
+ testDir = new File("build/HttpclientURLHandlerTest");
+ testDir.mkdirs();
+
+ handler = new HttpClientHandler();
+ }
+
+ @After
+ public void tearDown() {
+ try {
+ handler.close();
+ } catch (Exception e) {
+ // ignore
+ }
+ FileUtil.forceDelete(testDir);
+ }
+
+ @Test
+ public void testIsReachable() throws Exception {
+ assertTrue("URL resource was expected to be reachable", handler.isReachable(new URL("http://www.google.fr/"), defaultTimeoutConstraint));
+ assertFalse("URL resource was expected to be unreachable", handler.isReachable(new URL("http://www.google.fr/unknownpage.html"), defaultTimeoutConstraint));
+ }
+
+ @Test
+ public void testGetURLInfo() throws Exception {
+ // IVY-390
+ URLHandler handler = new HttpClientHandler();
+ URLInfo info = handler
+ .getURLInfo(new URL(
+ "https://repo1.maven.org/maven2/commons-lang/commons-lang/[1.0,3.0[/commons-lang-[1.0,3.0[.pom"), defaultTimeoutConstraint);
+
+ assertEquals(URLHandler.UNAVAILABLE, info);
+ }
+
+ @Test
+ public void testContentEncoding() throws Exception {
+ assertDownloadOK(new URL("http://carsten.codimi.de/gzip.yaws/daniels.html"), new File(
+ testDir, "gzip.txt"));
+ assertDownloadOK(new URL(
+ "http://carsten.codimi.de/gzip.yaws/daniels.html?deflate=on&zlib=on"), new File(
+ testDir, "deflate-zlib.txt"));
+ assertDownloadOK(new URL("http://carsten.codimi.de/gzip.yaws/daniels.html?deflate=on"),
+ new File(testDir, "deflate.txt"));
+ assertDownloadOK(new URL("http://carsten.codimi.de/gzip.yaws/a5.ps"), new File(testDir,
+ "a5-gzip.ps"));
+ assertDownloadOK(new URL("http://carsten.codimi.de/gzip.yaws/a5.ps?deflate=on"), new File(
+ testDir, "a5-deflate.ps"));
+ assertDownloadOK(new URL("http://carsten.codimi.de/gzip.yaws/nh80.pdf"), new File(testDir,
+ "nh80-gzip.pdf"));
+ assertDownloadOK(new URL("http://carsten.codimi.de/gzip.yaws/nh80.pdf?deflate=on"),
+ new File(testDir, "nh80-deflate.pdf"));
+ }
+
+ private void assertDownloadOK(final URL url, final File file) throws Exception {
+ handler.download(url, file, null, defaultTimeoutConstraint);
+ assertTrue("Content from " + url + " wasn't downloaded to " + file, file.exists());
+ assertTrue("Unexpected content at " + file + " for resource that was downloaded from " + url, file.isFile() && file.length() > 0);
+ }
+}
diff --git a/test/java/org/apache/ivy/util/url/HttpclientURLHandlerTest.java b/test/java/org/apache/ivy/util/url/HttpclientURLHandlerTest.java
index 80cdd1a3..0690c3ec 100644
--- a/test/java/org/apache/ivy/util/url/HttpclientURLHandlerTest.java
+++ b/test/java/org/apache/ivy/util/url/HttpclientURLHandlerTest.java
@@ -20,6 +20,8 @@ package org.apache.ivy.util.url;
import java.io.File;
import java.net.URL;
+import org.apache.ivy.core.settings.NamedTimeoutConstraint;
+import org.apache.ivy.core.settings.TimeoutConstraint;
import org.apache.ivy.util.FileUtil;
import org.apache.ivy.util.url.URLHandler.URLInfo;
@@ -32,13 +34,19 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
- * Test HttpClientHandler
+ * Test {@link HttpClientHandler}
*/
public class HttpclientURLHandlerTest {
// remote.test
private File testDir;
private HttpClientHandler handler;
+ private final TimeoutConstraint defaultTimeoutConstraint;
+
+ {
+ defaultTimeoutConstraint = new NamedTimeoutConstraint("default-http-client-handler-timeout");
+ ((NamedTimeoutConstraint) defaultTimeoutConstraint).setConnectionTimeout(5000);
+ }
@Before
public void setUp() {
@@ -50,13 +58,18 @@ public class HttpclientURLHandlerTest {
@After
public void tearDown() {
+ try {
+ handler.close();
+ } catch (Exception e) {
+ // ignore
+ }
FileUtil.forceDelete(testDir);
}
@Test
public void testIsReachable() throws Exception {
- assertTrue(handler.isReachable(new URL("http://www.google.fr/")));
- assertFalse(handler.isReachable(new URL("http://www.google.fr/unknownpage.html")));
+ assertTrue("URL resource was expected to be reachable", handler.isReachable(new URL("http://www.google.fr/"), defaultTimeoutConstraint));
+ assertFalse("URL resource was expected to be unreachable", handler.isReachable(new URL("http://www.google.fr/unknownpage.html"), defaultTimeoutConstraint));
}
@Test
@@ -65,7 +78,7 @@ public class HttpclientURLHandlerTest {
URLHandler handler = new HttpClientHandler();
URLInfo info = handler
.getURLInfo(new URL(
- "https://repo1.maven.org/maven2/commons-lang/commons-lang/[1.0,3.0[/commons-lang-[1.0,3.0[.pom"));
+ "https://repo1.maven.org/maven2/commons-lang/commons-lang/[1.0,3.0[/commons-lang-[1.0,3.0[.pom"), defaultTimeoutConstraint);
assertEquals(URLHandler.UNAVAILABLE, info);
}
@@ -89,8 +102,9 @@ public class HttpclientURLHandlerTest {
new File(testDir, "nh80-deflate.pdf"));
}
- private void assertDownloadOK(URL url, File file) throws Exception {
- handler.download(url, file, null);
- assertTrue(file.exists());
+ private void assertDownloadOK(final URL url, final File file) throws Exception {
+ handler.download(url, file, null, defaultTimeoutConstraint);
+ assertTrue("Content from " + url + " wasn't downloaded to " + file, file.exists());
+ assertTrue("Unexpected content at " + file + " for resource that was downloaded from " + url, file.isFile() && file.length() > 0);
}
}