package org.apache.maven.wagon.shared.http;

/*
 * 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.
 */

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
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.ChallengeState;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.NTCredentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.CookieSpecs;
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.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.DateUtils;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.SSLInitializationException;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.impl.auth.BasicScheme;
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.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.DefaultServiceUnavailableRetryStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.apache.maven.wagon.InputData;
import org.apache.maven.wagon.OutputData;
import org.apache.maven.wagon.PathUtils;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.StreamWagon;
import org.apache.maven.wagon.TransferFailedException;
import org.apache.maven.wagon.Wagon;
import org.apache.maven.wagon.authorization.AuthorizationException;
import org.apache.maven.wagon.events.TransferEvent;
import org.apache.maven.wagon.proxy.ProxyInfo;
import org.apache.maven.wagon.repository.Repository;
import org.apache.maven.wagon.resource.Resource;
import org.codehaus.plexus.util.StringUtils;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;

import static org.apache.maven.wagon.shared.http.HttpMessageUtils.formatAuthorizationMessage;
import static org.apache.maven.wagon.shared.http.HttpMessageUtils.formatResourceDoesNotExistMessage;
import static org.apache.maven.wagon.shared.http.HttpMessageUtils.formatTransferDebugMessage;
import static org.apache.maven.wagon.shared.http.HttpMessageUtils.formatTransferFailedMessage;

Author:Michal Maczka, James William Dumay
/** * @author <a href="michal.maczka@dimatics.com">Michal Maczka</a> * @author <a href="mailto:james@atlassian.com">James William Dumay</a> */
public abstract class AbstractHttpClientWagon extends StreamWagon { private final class WagonHttpEntity extends AbstractHttpEntity { private final Resource resource; private final Wagon wagon; private InputStream stream; private File source; private long length = -1; private boolean repeatable; private WagonHttpEntity( final InputStream stream, final Resource resource, final Wagon wagon, final File source ) throws TransferFailedException { if ( source != null ) { this.source = source; this.repeatable = true; } else { this.stream = stream; this.repeatable = false; } this.resource = resource; this.length = resource == null ? -1 : resource.getContentLength(); this.wagon = wagon; } public long getContentLength() { return length; } public InputStream getContent() throws IOException, IllegalStateException { if ( this.source != null ) { return new FileInputStream( this.source ); } return stream; } public boolean isRepeatable() { return repeatable; } public void writeTo( final OutputStream output ) throws IOException { if ( output == null ) { throw new NullPointerException( "output cannot be null" ); } TransferEvent transferEvent = new TransferEvent( wagon, resource, TransferEvent.TRANSFER_PROGRESS, TransferEvent.REQUEST_PUT ); transferEvent.setTimestamp( System.currentTimeMillis() ); try ( ReadableByteChannel input = ( this.source != null ) ? new RandomAccessFile( this.source, "r" ).getChannel() : Channels.newChannel( stream ) ) { ByteBuffer buffer = ByteBuffer.allocate( getBufferCapacityForTransfer( this.length ) ); int halfBufferCapacity = buffer.capacity() / 2; long remaining = this.length < 0L ? Long.MAX_VALUE : this.length; while ( remaining > 0L ) { int read = input.read( buffer ); if ( read == -1 ) { // EOF, but some data has not been written yet. if ( buffer.position() != 0 ) { buffer.flip(); fireTransferProgress( transferEvent, buffer.array(), buffer.limit() ); output.write( buffer.array(), 0, buffer.limit() ); buffer.clear(); } break; } // Prevent minichunking / fragmentation: when less than half the buffer is utilized, // read some more bytes before writing and firing progress. if ( buffer.position() < halfBufferCapacity ) { continue; } buffer.flip(); fireTransferProgress( transferEvent, buffer.array(), buffer.limit() ); output.write( buffer.array(), 0, buffer.limit() ); remaining -= buffer.limit(); buffer.clear(); } output.flush(); } } public boolean isStreaming() { return true; } } private static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone( "GMT" );
use http(s) connection pool mechanism. enabled by default
/** * use http(s) connection pool mechanism. * <b>enabled by default</b> */
private static boolean persistentPool = Boolean.valueOf( System.getProperty( "maven.wagon.http.pool", "true" ) );
skip failure on certificate validity checks. disabled by default
/** * skip failure on certificate validity checks. * <b>disabled by default</b> */
private static final boolean SSL_INSECURE = Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.insecure", "false" ) );
if using sslInsecure, certificate date issues will be ignored disabled by default
/** * if using sslInsecure, certificate date issues will be ignored * <b>disabled by default</b> */
private static final boolean IGNORE_SSL_VALIDITY_DATES = Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.ignore.validity.dates", "false" ) );
If enabled, ssl hostname verifier does not check hostname. Disable this will use a browser compat hostname verifier disabled by default
/** * If enabled, ssl hostname verifier does not check hostname. Disable this will use a browser compat hostname * verifier <b>disabled by default</b> */
private static final boolean SSL_ALLOW_ALL = Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.allowall", "false" ) );
Maximum concurrent connections per distinct route. 20 by default
/** * Maximum concurrent connections per distinct route. * <b>20 by default</b> */
private static final int MAX_CONN_PER_ROUTE = Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.maxPerRoute", "20" ) );
Maximum concurrent connections in total. 40 by default
/** * Maximum concurrent connections in total. * <b>40 by default</b> */
private static final int MAX_CONN_TOTAL = Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.maxTotal", "40" ) );
Time to live in seconds for an HTTP connection. After that time, the connection will be dropped. Intermediates tend to drop connections after some idle period. Set to -1 to maintain connections indefinitely. This value defaults to 300 seconds.
Since:3.2
/** * Time to live in seconds for an HTTP connection. After that time, the connection will be dropped. * Intermediates tend to drop connections after some idle period. Set to -1 to maintain connections * indefinitely. This value defaults to 300 seconds. * * @since 3.2 */
private static final long CONN_TTL = Long.getLong( "maven.wagon.httpconnectionManager.ttlSeconds", 300L );
Internal connection manager
/** * Internal connection manager */
private static HttpClientConnectionManager httpClientConnectionManager = createConnManager();
See RFC6585
/** * See RFC6585 */
protected static final int SC_TOO_MANY_REQUESTS = 429; /** * For exponential backoff. */
Initial seconds to back off when a HTTP 429 received. Subsequent 429 responses result in exponental backoff. 5 by default
Since:2.7
/** * Initial seconds to back off when a HTTP 429 received. * Subsequent 429 responses result in exponental backoff. * <b>5 by default</b> * * @since 2.7 */
private int initialBackoffSeconds = Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.backoffSeconds", "5" ) );
The maximum amount of time we want to back off in the case of repeated HTTP 429 response codes.
Since:2.7
/** * The maximum amount of time we want to back off in the case of * repeated HTTP 429 response codes. * * @since 2.7 */
private static final int MAX_BACKOFF_WAIT_SECONDS = Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.maxBackoffSeconds", "180" ) ); protected int backoff( int wait, String url ) throws InterruptedException, TransferFailedException { TimeUnit.SECONDS.sleep( wait ); int nextWait = wait * 2; if ( nextWait >= getMaxBackoffWaitSeconds() ) { throw new TransferFailedException( "Waited too long to access: " + url + ". Return code is: " + SC_TOO_MANY_REQUESTS ); } return nextWait; } @SuppressWarnings( "checkstyle:linelength" ) private static PoolingHttpClientConnectionManager createConnManager() { String sslProtocolsStr = System.getProperty( "https.protocols" ); String cipherSuitesStr = System.getProperty( "https.cipherSuites" ); String[] sslProtocols = sslProtocolsStr != null ? sslProtocolsStr.split( " *, *" ) : null; String[] cipherSuites = cipherSuitesStr != null ? cipherSuitesStr.split( " *, *" ) : null; SSLConnectionSocketFactory sslConnectionSocketFactory; if ( SSL_INSECURE ) { try { SSLContext sslContext = new SSLContextBuilder().useSSL().loadTrustMaterial( null, new RelaxedTrustStrategy( IGNORE_SSL_VALIDITY_DATES ) ).build(); sslConnectionSocketFactory = new SSLConnectionSocketFactory( sslContext, sslProtocols, cipherSuites, SSL_ALLOW_ALL ? SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER : SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER ); } catch ( Exception ex ) { throw new SSLInitializationException( ex.getMessage(), ex ); } } else { sslConnectionSocketFactory = new SSLConnectionSocketFactory( HttpsURLConnection.getDefaultSSLSocketFactory(), sslProtocols, cipherSuites, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER ); } Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create().register( "http", PlainConnectionSocketFactory.INSTANCE ).register( "https", sslConnectionSocketFactory ).build(); PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager( registry, null, null, null, CONN_TTL, TimeUnit.SECONDS ); if ( persistentPool ) { connManager.setDefaultMaxPerRoute( MAX_CONN_PER_ROUTE ); connManager.setMaxTotal( MAX_CONN_TOTAL ); } else { connManager.setMaxTotal( 1 ); } return connManager; }
The type of the retry handler, defaults to standard. Values can be default DefaultHttpRequestRetryHandler, or StandardHttpRequestRetryHandler, or a fully qualified name class with a no-arg.
Since:3.2
/** * The type of the retry handler, defaults to {@code standard}. * Values can be {@link default DefaultHttpRequestRetryHandler}, * or {@link standard StandardHttpRequestRetryHandler}, * or a fully qualified name class with a no-arg. * * @since 3.2 */
private static final String RETRY_HANDLER_CLASS = System.getProperty( "maven.wagon.http.retryHandler.class", "standard" );
Whether or not methods that have successfully sent their request will be retried, defaults to false. Note: only used for default and standard retry handlers.
Since:3.2
/** * Whether or not methods that have successfully sent their request will be retried, * defaults to {@code false}. * Note: only used for default and standard retry handlers. * * @since 3.2 */
private static final boolean RETRY_HANDLER_REQUEST_SENT_ENABLED = Boolean.getBoolean( "maven.wagon.http.retryHandler.requestSentEnabled" );
Number of retries for the retry handler, defaults to 3. Note: only used for default and standard retry handlers.
Since:3.2
/** * Number of retries for the retry handler, defaults to 3. * Note: only used for default and standard retry handlers. * * @since 3.2 */
private static final int RETRY_HANDLER_COUNT = Integer.getInteger( "maven.wagon.http.retryHandler.count", 3 );
Comma-separated list of non-retryable exception classes. Note: only used for default retry handler.
Since:3.2
/** * Comma-separated list of non-retryable exception classes. * Note: only used for default retry handler. * * @since 3.2 */
private static final String RETRY_HANDLER_EXCEPTIONS = System.getProperty( "maven.wagon.http.retryHandler.nonRetryableClasses" ); private static HttpRequestRetryHandler createRetryHandler() { switch ( RETRY_HANDLER_CLASS ) { case "default": if ( StringUtils.isEmpty( RETRY_HANDLER_EXCEPTIONS ) ) { return new DefaultHttpRequestRetryHandler( RETRY_HANDLER_COUNT, RETRY_HANDLER_REQUEST_SENT_ENABLED ); } return new DefaultHttpRequestRetryHandler( RETRY_HANDLER_COUNT, RETRY_HANDLER_REQUEST_SENT_ENABLED, getNonRetryableExceptions() ) { }; case "standard": return new StandardHttpRequestRetryHandler( RETRY_HANDLER_COUNT, RETRY_HANDLER_REQUEST_SENT_ENABLED ); default: try { final ClassLoader classLoader = AbstractHttpClientWagon.class.getClassLoader(); return HttpRequestRetryHandler.class.cast( classLoader.loadClass( RETRY_HANDLER_CLASS ) .getConstructor().newInstance() ); } catch ( final Exception e ) { throw new IllegalArgumentException( e ); } } }
The type of the serviceUnavailableRetryStrategy, defaults to none. Values can be default DefaultServiceUnavailableRetryStrategy, or StandardServiceUnavailableRetryStrategy, or a fully qualified name class with a no-arg or none to not use a ServiceUnavailableRetryStrategy.
/** * The type of the serviceUnavailableRetryStrategy, defaults to {@code none}. * Values can be {@link default DefaultServiceUnavailableRetryStrategy}, * or {@link standard StandardServiceUnavailableRetryStrategy}, or * a fully qualified name class with a no-arg or none to not use a ServiceUnavailableRetryStrategy. */
private static final String SERVICE_UNAVAILABLE_RETRY_STRATEGY_CLASS = System.getProperty( "maven.wagon.http.serviceUnavailableRetryStrategy.class", "none" );
Interval in milliseconds between retries when using a serviceUnavailableRetryStrategy. 1000 by default
/** * Interval in milliseconds between retries when using a serviceUnavailableRetryStrategy. * <b>1000 by default</b> */
private static final int SERVICE_UNAVAILABLE_RETRY_STRATEGY_RETRY_INTERVAL = Integer.getInteger( "maven.wagon.http.serviceUnavailableRetryStrategy.retryInterval", 1000 );
Maximum number of retries when using a serviceUnavailableRetryStrategy. 5 by default
/** * Maximum number of retries when using a serviceUnavailableRetryStrategy. * <b>5 by default</b> */
private static final int SERVICE_UNAVAILABLE_RETRY_STRATEGY_MAX_RETRIES = Integer.getInteger( "maven.wagon.http.serviceUnavailableRetryStrategy.maxRetries", 5 ); private static ServiceUnavailableRetryStrategy createServiceUnavailableRetryStrategy() { switch ( SERVICE_UNAVAILABLE_RETRY_STRATEGY_CLASS ) { case "none": return null; case "default": return new DefaultServiceUnavailableRetryStrategy( SERVICE_UNAVAILABLE_RETRY_STRATEGY_MAX_RETRIES, SERVICE_UNAVAILABLE_RETRY_STRATEGY_RETRY_INTERVAL ); case "standard": return new StandardServiceUnavailableRetryStrategy( SERVICE_UNAVAILABLE_RETRY_STRATEGY_MAX_RETRIES, SERVICE_UNAVAILABLE_RETRY_STRATEGY_RETRY_INTERVAL ); default: try { final ClassLoader classLoader = AbstractHttpClientWagon.class.getClassLoader(); return ServiceUnavailableRetryStrategy.class.cast( classLoader.loadClass( SERVICE_UNAVAILABLE_RETRY_STRATEGY_CLASS ) .getConstructor().newInstance() ); } catch ( final Exception e ) { throw new IllegalArgumentException( e ); } } } private static Registry<AuthSchemeProvider> createAuthSchemeRegistry() { return RegistryBuilder.<AuthSchemeProvider>create() .register( AuthSchemes.BASIC, new BasicSchemeFactory( StandardCharsets.UTF_8 ) ) .register( AuthSchemes.DIGEST, new DigestSchemeFactory( StandardCharsets.UTF_8 ) ) .register( AuthSchemes.NTLM, new NTLMSchemeFactory() ) .build(); } private static Collection<Class<? extends IOException>> getNonRetryableExceptions() { final List<Class<? extends IOException>> exceptions = new ArrayList<>(); final ClassLoader loader = AbstractHttpClientWagon.class.getClassLoader(); for ( final String ex : RETRY_HANDLER_EXCEPTIONS.split( "," ) ) { try { exceptions.add( ( Class<? extends IOException> ) loader.loadClass( ex ) ); } catch ( final ClassNotFoundException e ) { throw new IllegalArgumentException( e ); } } return exceptions; } private static CloseableHttpClient httpClient = createClient(); private static CloseableHttpClient createClient() { return HttpClientBuilder.create() // .useSystemProperties() // .disableConnectionState() // .setConnectionManager( httpClientConnectionManager ) // .setRetryHandler( createRetryHandler() ) .setServiceUnavailableRetryStrategy( createServiceUnavailableRetryStrategy() ) .setDefaultAuthSchemeRegistry( createAuthSchemeRegistry() ) .build(); } private CredentialsProvider credentialsProvider; private AuthCache authCache; private Closeable closeable;
@plexus.configuration
Deprecated:Use httpConfiguration instead.
/** * @plexus.configuration * @deprecated Use httpConfiguration instead. */
private Properties httpHeaders;
Since:1.0-beta-6
/** * @since 1.0-beta-6 */
private HttpConfiguration httpConfiguration;
Basic auth scope overrides
Since:2.8
/** * Basic auth scope overrides * @since 2.8 */
private BasicAuthScope basicAuth;
Proxy basic auth scope overrides
Since:2.8
/** * Proxy basic auth scope overrides * @since 2.8 */
private BasicAuthScope proxyAuth; public void openConnectionInternal() { repository.setUrl( getURL( repository ) ); credentialsProvider = new BasicCredentialsProvider(); authCache = new BasicAuthCache(); if ( authenticationInfo != null ) { String username = authenticationInfo.getUserName(); String password = authenticationInfo.getPassword(); if ( StringUtils.isNotEmpty( username ) && StringUtils.isNotEmpty( password ) ) { Credentials creds = new UsernamePasswordCredentials( username, password ); String host = getRepository().getHost(); int port = getRepository().getPort(); credentialsProvider.setCredentials( getBasicAuthScope().getScope( host, port ), creds ); } } ProxyInfo proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() ); if ( proxyInfo != null ) { String proxyUsername = proxyInfo.getUserName(); String proxyPassword = proxyInfo.getPassword(); String proxyHost = proxyInfo.getHost(); String proxyNtlmHost = proxyInfo.getNtlmHost(); String proxyNtlmDomain = proxyInfo.getNtlmDomain(); if ( proxyHost != null ) { if ( proxyUsername != null && proxyPassword != null ) { Credentials creds; if ( proxyNtlmHost != null || proxyNtlmDomain != null ) { creds = new NTCredentials( proxyUsername, proxyPassword, proxyNtlmHost, proxyNtlmDomain ); } else { creds = new UsernamePasswordCredentials( proxyUsername, proxyPassword ); } int proxyPort = proxyInfo.getPort(); AuthScope authScope = getProxyBasicAuthScope().getScope( proxyHost, proxyPort ); credentialsProvider.setCredentials( authScope, creds ); } } } } public void closeConnection() { if ( !persistentPool ) { httpClientConnectionManager.closeIdleConnections( 0, TimeUnit.MILLISECONDS ); } if ( authCache != null ) { authCache.clear(); authCache = null; } if ( credentialsProvider != null ) { credentialsProvider.clear(); credentialsProvider = null; } } public static CloseableHttpClient getHttpClient() { return httpClient; } public static void setPersistentPool( boolean persistentPool ) { persistentPool = persistentPool; } public static void setPoolingHttpClientConnectionManager( PoolingHttpClientConnectionManager poolingHttpClientConnectionManager ) { httpClientConnectionManager = poolingHttpClientConnectionManager; httpClient = createClient(); } public void put( File source, String resourceName ) throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException { Resource resource = new Resource( resourceName ); firePutInitiated( resource, source ); resource.setContentLength( source.length() ); resource.setLastModified( source.lastModified() ); put( null, resource, source ); } public void putFromStream( final InputStream stream, String destination, long contentLength, long lastModified ) throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException { Resource resource = new Resource( destination ); firePutInitiated( resource, null ); resource.setContentLength( contentLength ); resource.setLastModified( lastModified ); put( stream, resource, null ); } private void put( final InputStream stream, Resource resource, File source ) throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException { put( resource, source, new WagonHttpEntity( stream, resource, this, source ) ); } private void put( Resource resource, File source, HttpEntity httpEntity ) throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException { put( resource, source, httpEntity, buildUrl( resource ) ); }
Builds a complete URL string from the repository URL and the relative path of the resource passed.
Params:
  • resource – the resource to extract the relative path from.
Returns:the complete URL
/** * Builds a complete URL string from the repository URL and the relative path of the resource passed. * * @param resource the resource to extract the relative path from. * @return the complete URL */
private String buildUrl( Resource resource ) { return buildUrl( resource.getName() ); }
Builds a complete URL string from the repository URL and the relative path of the resource passed.
Params:
  • resourceName – the resourcerelative path
Returns:the complete URL
/** * Builds a complete URL string from the repository URL and the relative path of the resource passed. * * @param resourceName the resourcerelative path * @return the complete URL */
private String buildUrl( String resourceName ) { return EncodingUtil.encodeURLToString( getRepository().getUrl(), resourceName ); } private void put( Resource resource, File source, HttpEntity httpEntity, String url ) throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException { put( getInitialBackoffSeconds(), resource, source, httpEntity, url ); } private void put( int wait, Resource resource, File source, HttpEntity httpEntity, String url ) throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException { //Parent directories need to be created before posting try { mkdirs( PathUtils.dirname( resource.getName() ) ); } catch ( HttpException he ) { fireTransferError( resource, he, TransferEvent.REQUEST_PUT ); } catch ( IOException e ) { fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); } // preemptive for put // TODO: is it a good idea, though? 'Expect-continue' handshake would serve much better Repository repo = getRepository(); HttpHost targetHost = new HttpHost( repo.getHost(), repo.getPort(), repo.getProtocol() ); AuthScope targetScope = getBasicAuthScope().getScope( targetHost ); if ( credentialsProvider.getCredentials( targetScope ) != null ) { BasicScheme targetAuth = new BasicScheme(); authCache.put( targetHost, targetAuth ); } HttpPut putMethod = new HttpPut( url ); firePutStarted( resource, source ); try { putMethod.setEntity( httpEntity ); CloseableHttpResponse response = execute( putMethod ); try { fireTransferDebug( formatTransferDebugMessage( url, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), getProxyInfo() ) ); int statusCode = response.getStatusLine().getStatusCode(); // Check that we didn't run out of retries. switch ( statusCode ) { // Success Codes case HttpStatus.SC_OK: // 200 case HttpStatus.SC_CREATED: // 201 case HttpStatus.SC_ACCEPTED: // 202 case HttpStatus.SC_NO_CONTENT: // 204 break; // handle all redirect even if http specs says " the user agent MUST NOT automatically redirect // the request unless it can be confirmed by the user" case HttpStatus.SC_MOVED_PERMANENTLY: // 301 case HttpStatus.SC_MOVED_TEMPORARILY: // 302 case HttpStatus.SC_SEE_OTHER: // 303 put( resource, source, httpEntity, calculateRelocatedUrl( response ) ); return; //case HttpStatus.SC_UNAUTHORIZED: case HttpStatus.SC_FORBIDDEN: fireSessionConnectionRefused(); throw new AuthorizationException( formatAuthorizationMessage( url, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), getProxyInfo() ) ); case HttpStatus.SC_NOT_FOUND: throw new ResourceDoesNotExistException( formatResourceDoesNotExistMessage( url, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), getProxyInfo() ) ); case SC_TOO_MANY_REQUESTS: put( backoff( wait, url ), resource, source, httpEntity, url ); break; //add more entries here default: TransferFailedException e = new TransferFailedException( formatTransferFailedMessage( url, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), getProxyInfo() ) ); fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); throw e; } firePutCompleted( resource, source ); EntityUtils.consume( response.getEntity() ); } finally { response.close(); } } catch ( IOException | HttpException | InterruptedException e ) { fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); throw new TransferFailedException( formatTransferFailedMessage( url, getProxyInfo() ), e ); } } protected String calculateRelocatedUrl( HttpResponse response ) { Header locationHeader = response.getFirstHeader( "Location" ); String locationField = locationHeader.getValue(); // is it a relative Location or a full ? return locationField.startsWith( "http" ) ? locationField : getURL( getRepository() ) + '/' + locationField; } protected void mkdirs( String dirname ) throws HttpException, IOException { // nothing to do } public boolean resourceExists( String resourceName ) throws TransferFailedException, AuthorizationException { return resourceExists( getInitialBackoffSeconds(), resourceName ); } private boolean resourceExists( int wait, String resourceName ) throws TransferFailedException, AuthorizationException { String url = buildUrl( resourceName ); HttpHead headMethod = new HttpHead( url ); try { CloseableHttpResponse response = execute( headMethod ); try { int statusCode = response.getStatusLine().getStatusCode(); boolean result; switch ( statusCode ) { case HttpStatus.SC_OK: result = true; break; case HttpStatus.SC_NOT_MODIFIED: result = true; break; case HttpStatus.SC_FORBIDDEN: case HttpStatus.SC_UNAUTHORIZED: case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: throw new AuthorizationException( formatAuthorizationMessage( url, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), getProxyInfo() ) ); case HttpStatus.SC_NOT_FOUND: result = false; break; case SC_TOO_MANY_REQUESTS: return resourceExists( backoff( wait, resourceName ), resourceName ); //add more entries here default: throw new TransferFailedException( formatTransferFailedMessage( url, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), getProxyInfo() ) ); } EntityUtils.consume( response.getEntity() ); return result; } finally { response.close(); } } catch ( IOException | HttpException | InterruptedException e ) { throw new TransferFailedException( formatTransferFailedMessage( url, getProxyInfo() ), e ); } } protected CloseableHttpResponse execute( HttpUriRequest httpMethod ) throws HttpException, IOException { setHeaders( httpMethod ); String userAgent = getUserAgent( httpMethod ); if ( userAgent != null ) { httpMethod.setHeader( HTTP.USER_AGENT, userAgent ); } RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); // WAGON-273: default the cookie-policy to browser compatible requestConfigBuilder.setCookieSpec( CookieSpecs.BROWSER_COMPATIBILITY ); Repository repo = getRepository(); ProxyInfo proxyInfo = getProxyInfo( repo.getProtocol(), repo.getHost() ); if ( proxyInfo != null ) { HttpHost proxy = new HttpHost( proxyInfo.getHost(), proxyInfo.getPort() ); requestConfigBuilder.setProxy( proxy ); } HttpMethodConfiguration config = httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( httpMethod ); if ( config != null ) { ConfigurationUtils.copyConfig( config, requestConfigBuilder ); } else { requestConfigBuilder.setSocketTimeout( getReadTimeout() ); if ( httpMethod instanceof HttpPut ) { requestConfigBuilder.setExpectContinueEnabled( true ); } } if ( httpMethod instanceof HttpPut ) { requestConfigBuilder.setRedirectsEnabled( false ); } HttpClientContext localContext = HttpClientContext.create(); localContext.setCredentialsProvider( credentialsProvider ); localContext.setAuthCache( authCache ); localContext.setRequestConfig( requestConfigBuilder.build() ); if ( config != null && config.isUsePreemptive() ) { HttpHost targetHost = new HttpHost( repo.getHost(), repo.getPort(), repo.getProtocol() ); AuthScope targetScope = getBasicAuthScope().getScope( targetHost ); if ( credentialsProvider.getCredentials( targetScope ) != null ) { BasicScheme targetAuth = new BasicScheme(); authCache.put( targetHost, targetAuth ); } } if ( proxyInfo != null ) { if ( proxyInfo.getHost() != null ) { HttpHost proxyHost = new HttpHost( proxyInfo.getHost(), proxyInfo.getPort() ); AuthScope proxyScope = getProxyBasicAuthScope().getScope( proxyHost ); if ( credentialsProvider.getCredentials( proxyScope ) != null ) { /* This is extremely ugly because we need to set challengeState to PROXY, but * the constructor is deprecated. Alternatively, we could subclass BasicScheme * to ProxyBasicScheme and set the state internally in the constructor. */ BasicScheme proxyAuth = new BasicScheme( ChallengeState.PROXY ); authCache.put( proxyHost, proxyAuth ); } } } return httpClient.execute( httpMethod, localContext ); } public void setHeaders( HttpUriRequest method ) { HttpMethodConfiguration config = httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method ); if ( config == null || config.isUseDefaultHeaders() ) { // TODO: merge with the other headers and have some better defaults, unify with lightweight headers method.addHeader( "Cache-control", "no-cache" ); method.addHeader( "Cache-store", "no-store" ); method.addHeader( "Pragma", "no-cache" ); } if ( httpHeaders != null ) { for ( Map.Entry<Object, Object> entry : httpHeaders.entrySet() ) { method.setHeader( (String) entry.getKey(), (String) entry.getValue() ); } } Header[] headers = config == null ? null : config.asRequestHeaders(); if ( headers != null ) { for ( Header header : headers ) { method.setHeader( header ); } } Header userAgentHeader = method.getFirstHeader( HTTP.USER_AGENT ); if ( userAgentHeader == null ) { String userAgent = getUserAgent( method ); if ( userAgent != null ) { method.setHeader( HTTP.USER_AGENT, userAgent ); } } } protected String getUserAgent( HttpUriRequest method ) { if ( httpHeaders != null ) { String value = (String) httpHeaders.get( HTTP.USER_AGENT ); if ( value != null ) { return value; } } HttpMethodConfiguration config = httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method ); if ( config != null ) { return (String) config.getHeaders().get( HTTP.USER_AGENT ); } return null; }
getUrl Implementors can override this to remove unwanted parts of the url such as role-hints
Params:
  • repository –
Returns:
/** * getUrl * Implementors can override this to remove unwanted parts of the url such as role-hints * * @param repository * @return */
protected String getURL( Repository repository ) { return repository.getUrl(); } public HttpConfiguration getHttpConfiguration() { return httpConfiguration; } public void setHttpConfiguration( HttpConfiguration httpConfiguration ) { this.httpConfiguration = httpConfiguration; }
Get the override values for standard HttpClient AuthScope
Returns:the basicAuth
/** * Get the override values for standard HttpClient AuthScope * * @return the basicAuth */
public BasicAuthScope getBasicAuthScope() { if ( basicAuth == null ) { basicAuth = new BasicAuthScope(); } return basicAuth; }
Set the override values for standard HttpClient AuthScope
Params:
  • basicAuth – the AuthScope to set
/** * Set the override values for standard HttpClient AuthScope * * @param basicAuth the AuthScope to set */
public void setBasicAuthScope( BasicAuthScope basicAuth ) { this.basicAuth = basicAuth; }
Get the override values for proxy HttpClient AuthScope
Returns:the proxyAuth
/** * Get the override values for proxy HttpClient AuthScope * * @return the proxyAuth */
public BasicAuthScope getProxyBasicAuthScope() { if ( proxyAuth == null ) { proxyAuth = new BasicAuthScope(); } return proxyAuth; }
Set the override values for proxy HttpClient AuthScope
Params:
  • proxyAuth – the AuthScope to set
/** * Set the override values for proxy HttpClient AuthScope * * @param proxyAuth the AuthScope to set */
public void setProxyBasicAuthScope( BasicAuthScope proxyAuth ) { this.proxyAuth = proxyAuth; } public void fillInputData( InputData inputData ) throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException { fillInputData( getInitialBackoffSeconds(), inputData ); } private void fillInputData( int wait, InputData inputData ) throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException { Resource resource = inputData.getResource(); String url = buildUrl( resource ); HttpGet getMethod = new HttpGet( url ); long timestamp = resource.getLastModified(); if ( timestamp > 0 ) { SimpleDateFormat fmt = new SimpleDateFormat( "EEE, dd-MMM-yy HH:mm:ss zzz", Locale.US ); fmt.setTimeZone( GMT_TIME_ZONE ); Header hdr = new BasicHeader( "If-Modified-Since", fmt.format( new Date( timestamp ) ) ); fireTransferDebug( "sending ==> " + hdr + "(" + timestamp + ")" ); getMethod.addHeader( hdr ); } try { CloseableHttpResponse response = execute( getMethod ); closeable = response; fireTransferDebug( formatTransferDebugMessage( url, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), getProxyInfo() ) ); int statusCode = response.getStatusLine().getStatusCode(); switch ( statusCode ) { case HttpStatus.SC_OK: break; case HttpStatus.SC_NOT_MODIFIED: // return, leaving last modified set to original value so getIfNewer should return unmodified return; case HttpStatus.SC_FORBIDDEN: case HttpStatus.SC_UNAUTHORIZED: case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: fireSessionConnectionRefused(); throw new AuthorizationException( formatAuthorizationMessage( url, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), getProxyInfo() ) ); case HttpStatus.SC_NOT_FOUND: throw new ResourceDoesNotExistException( formatResourceDoesNotExistMessage( url, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), getProxyInfo() ) ); case SC_TOO_MANY_REQUESTS: fillInputData( backoff( wait, url ), inputData ); break; // add more entries here default: cleanupGetTransfer( resource ); TransferFailedException e = new TransferFailedException( formatTransferFailedMessage( url, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), getProxyInfo() ) ); fireTransferError( resource, e, TransferEvent.REQUEST_GET ); throw e; } Header contentLengthHeader = response.getFirstHeader( "Content-Length" ); if ( contentLengthHeader != null ) { try { long contentLength = Long.parseLong( contentLengthHeader.getValue() ); resource.setContentLength( contentLength ); } catch ( NumberFormatException e ) { fireTransferDebug( "error parsing content length header '" + contentLengthHeader.getValue() + "' " + e ); } } Header lastModifiedHeader = response.getFirstHeader( "Last-Modified" ); if ( lastModifiedHeader != null ) { Date lastModified = DateUtils.parseDate( lastModifiedHeader.getValue() ); if ( lastModified != null ) { resource.setLastModified( lastModified.getTime() ); fireTransferDebug( "last-modified = " + lastModifiedHeader.getValue() + " (" + lastModified.getTime() + ")" ); } } HttpEntity entity = response.getEntity(); if ( entity != null ) { inputData.setInputStream( entity.getContent() ); } } catch ( IOException | HttpException | InterruptedException e ) { fireTransferError( resource, e, TransferEvent.REQUEST_GET ); throw new TransferFailedException( formatTransferFailedMessage( url, getProxyInfo() ), e ); } } protected void cleanupGetTransfer( Resource resource ) { if ( closeable != null ) { try { closeable.close(); } catch ( IOException ignore ) { // ignore } } } @Override public void putFromStream( InputStream stream, String destination ) throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException { putFromStream( stream, destination, -1, -1 ); } @Override protected void putFromStream( InputStream stream, Resource resource ) throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException { putFromStream( stream, resource.getName(), -1, -1 ); } public Properties getHttpHeaders() { return httpHeaders; } public void setHttpHeaders( Properties httpHeaders ) { this.httpHeaders = httpHeaders; } @Override public void fillOutputData( OutputData outputData ) throws TransferFailedException { // no needed in this implementation but throw an Exception if used throw new IllegalStateException( "this wagon http client must not use fillOutputData" ); } protected CredentialsProvider getCredentialsProvider() { return credentialsProvider; } protected AuthCache getAuthCache() { return authCache; } public int getInitialBackoffSeconds() { return initialBackoffSeconds; } public void setInitialBackoffSeconds( int initialBackoffSeconds ) { this.initialBackoffSeconds = initialBackoffSeconds; } public static int getMaxBackoffWaitSeconds() { return MAX_BACKOFF_WAIT_SECONDS; } }