package org.sonatype.nexus.ahc;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.sonatype.nexus.configuration.application.ApplicationConfiguration;
import org.sonatype.nexus.proxy.repository.ClientSSLRemoteAuthenticationSettings;
import org.sonatype.nexus.proxy.repository.NtlmRemoteAuthenticationSettings;
import org.sonatype.nexus.proxy.repository.ProxyRepository;
import org.sonatype.nexus.proxy.repository.RemoteAuthenticationSettings;
import org.sonatype.nexus.proxy.repository.RemoteProxySettings;
import org.sonatype.nexus.proxy.repository.UsernamePasswordRemoteAuthenticationSettings;
import org.sonatype.nexus.proxy.storage.remote.RemoteStorageContext;
import org.sonatype.nexus.proxy.utils.UserAgentBuilder;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.AsyncHttpClientConfig.Builder;
import com.ning.http.client.ProxyServer;
import com.ning.http.client.Realm;
@Component( role = AhcProvider.class )
public class DefaultAhcProvider
implements AhcProvider
{
@Requirement
private ApplicationConfiguration applicationConfiguration;
@Requirement
private UserAgentBuilder userAgentBuilder;
private volatile AsyncHttpClient sharedClient;
private volatile boolean shutdown = false;
public synchronized void reset()
{
sharedClient = null;
}
public synchronized void close()
{
try
{
if ( sharedClient != null )
{
sharedClient.close();
}
}
finally
{
shutdown = true;
}
}
@Override
public synchronized AsyncHttpClient getAsyncHttpClient()
{
if ( shutdown )
{
throw new IllegalStateException( "AHC provider was shut down, not serving client up anymore." );
}
if ( sharedClient == null )
{
// TODO: nexus wide singleton or new instance per invocation?
final Builder configBuilder =
getAsyncHttpClientConfigBuilder( applicationConfiguration.getGlobalRemoteStorageContext() );
configBuilder.setUserAgent( userAgentBuilder.formatGenericUserAgentString() );
sharedClient = new AsyncHttpClient( configBuilder.build() );
}
return sharedClient;
}
@Override
public Builder getAsyncHttpClientConfigBuilder( final ProxyRepository repository, final RemoteStorageContext ctx )
{
if ( shutdown )
{
throw new IllegalStateException( "AHC provider was shut down, not serving client up anymore." );
}
final Builder result = getAsyncHttpClientConfigBuilder( ctx );
result.setUserAgent( userAgentBuilder.formatRemoteRepositoryStorageUserAgentString( repository, ctx ) );
// enable redirects for RRS use
result.setFollowRedirects( true );
// limiting (with httpClient defaults) to prevent bashing of remote repositories
result.setMaximumConnectionsPerHost( 20 );
result.setMaximumConnectionsTotal( 20 );
// proxy-logic will handle retries
result.setMaxRequestRetry( 0 );
return result;
}
// ==
protected Builder getAsyncHttpClientConfigBuilder( final RemoteStorageContext ctx )
{
final AsyncHttpClientConfig.Builder result = new AsyncHttpClientConfig.Builder();
// timeout
final int timeout = ctx.getRemoteConnectionSettings().getConnectionTimeout();
result.setConnectionTimeoutInMs( timeout );
result.setRequestTimeoutInMs( timeout );
// handle compression
result.setCompressionEnabled( true );
// remote auth
RemoteAuthenticationSettings ras = ctx.getRemoteAuthenticationSettings();
if ( ras != null )
{
Realm realm = null;
if ( ras instanceof ClientSSLRemoteAuthenticationSettings )
{
// ClientSSLRemoteAuthenticationSettings cras = (ClientSSLRemoteAuthenticationSettings) ras;
// TODO - implement this
}
else if ( ras instanceof NtlmRemoteAuthenticationSettings )
{
NtlmRemoteAuthenticationSettings nras = (NtlmRemoteAuthenticationSettings) ras;
realm =
new Realm.RealmBuilder().setPrincipal( nras.getUsername() ).setPassword( nras.getPassword() ).setNtlmDomain(
nras.getNtlmDomain() ).setNtlmHost( nras.getNtlmHost() ).build();
}
else if ( ras instanceof UsernamePasswordRemoteAuthenticationSettings )
{
UsernamePasswordRemoteAuthenticationSettings uras = (UsernamePasswordRemoteAuthenticationSettings) ras;
realm =
new Realm.RealmBuilder().setPrincipal( uras.getUsername() ).setPassword( uras.getPassword() ).setUsePreemptiveAuth(
true ).build();
}
if ( realm != null )
{
result.setRealm( realm );
}
}
// proxy
RemoteProxySettings rps = ctx.getRemoteProxySettings();
if ( rps.isEnabled() )
{
ProxyServer proxy = null;
if ( rps.getProxyAuthentication() != null )
{
ras = rps.getProxyAuthentication();
if ( ras instanceof ClientSSLRemoteAuthenticationSettings )
{
// ClientSSLRemoteAuthenticationSettings cras = (ClientSSLRemoteAuthenticationSettings) ras;
// TODO - implement this
}
else if ( ras instanceof NtlmRemoteAuthenticationSettings )
{
NtlmRemoteAuthenticationSettings nras = (NtlmRemoteAuthenticationSettings) ras;
proxy = new ProxyServer( rps.getHostname(), rps.getPort(), nras.getUsername(), nras.getPassword() );
proxy.setNtlmDomain( nras.getNtlmDomain() );
}
else if ( ras instanceof UsernamePasswordRemoteAuthenticationSettings )
{
UsernamePasswordRemoteAuthenticationSettings uras =
(UsernamePasswordRemoteAuthenticationSettings) ras;
proxy = new ProxyServer( rps.getHostname(), rps.getPort(), uras.getUsername(), uras.getPassword() );
}
}
else
{
proxy = new ProxyServer( rps.getHostname(), rps.getPort() );
}
// to avoid NPEs while incomplete
if ( proxy != null )
{
// check if we have non-proxy hosts
if ( rps.getNonProxyHosts() != null && !rps.getNonProxyHosts().isEmpty() )
{
// TODO: someone "invented" non-proxy hosts as a list of regexps! So, question is, how to
// transport that "smart move" to AHC?
// AHC supports "*" as wildcard
for ( String nonProxyHost : rps.getNonProxyHosts() )
{
proxy.addNonProxyHost( nonProxyHost );
}
}
result.setProxyServer( proxy );
}
}
return result;
}
}