package org.limewire.http.httpclient; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; import java.security.NoSuchAlgorithmException; import java.util.concurrent.ScheduledExecutorService; import javax.net.ssl.SSLContext; import org.apache.http.client.HttpClient; import org.apache.http.client.params.ClientPNames; import org.apache.http.client.params.HttpClientParams; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.scheme.SocketFactory; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.limewire.concurrent.AbstractLazySingletonProvider; import org.limewire.net.SocketsManager; import org.limewire.nio.NBSocket; import com.google.inject.AbstractModule; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Scopes; import com.google.inject.Singleton; import com.google.inject.name.Named; import com.google.inject.name.Names; public class LimeWireHttpClientModule extends AbstractModule { @Override protected void configure() { bind(ReapingClientConnectionManager.class).annotatedWith(Names.named("nonBlockingConnectionManager")).toProvider(LimeClientConnectionManagerProvider.class).in(Scopes.SINGLETON); bind(ReapingClientConnectionManager.class).annotatedWith(Names.named("socketWrappingConnectionManager")).toProvider(SocketWrappingClientConnectionManagerProvider.class).in(Scopes.SINGLETON); bind(ClientConnectionManager.class).annotatedWith(Names.named("sslConnectionManager")).toProvider(SSLClientConnectionManagerProvider.class).in(Scopes.SINGLETON); bind(LimeHttpClient.class).toProvider(NonBlockingLimeHttpClientProvider.class); bind(HttpClient.class).toProvider(NonBlockingLimeHttpClientProvider.class); bind(SocketWrappingHttpClient.class).toProvider(SocketWrappingLimeHttpClientProvider.class); bind(SchemeRegistry.class).annotatedWith(Names.named("limeSchemeRegistry")).toProvider(LimeSchemeRegistryProvider.class); bind(SchemeRegistry.class).annotatedWith(Names.named("socketWrappingSchemeRegistry")).toProvider(SocketWrappingSchemeRegistryProvider.class); bind(SchemeRegistry.class).annotatedWith(Names.named("sslSchemeRegistry")).toProvider(SSLSchemeRegistryProvider.class); bind(SocketWrapperProtocolSocketFactory.class); bind(HttpParams.class).annotatedWith(Names.named("defaults")).toProvider(DefaultHttpParamsProvider.class); } private abstract static class AbstractLimeHttpClientProvider implements Provider<SocketWrappingHttpClient> { private ReapingClientConnectionManager manager; private final Provider<HttpParams> defaultParams; public AbstractLimeHttpClientProvider(ReapingClientConnectionManager manager, Provider<HttpParams> defaultParams) { this.manager = manager; this.defaultParams = defaultParams; } public SocketWrappingHttpClient get() { return new LimeHttpClientImpl(manager, defaultParams); } } @Singleton private static class NonBlockingLimeHttpClientProvider extends AbstractLimeHttpClientProvider { @Inject public NonBlockingLimeHttpClientProvider(@Named("nonBlockingConnectionManager") ReapingClientConnectionManager manager, @Named("defaults") Provider<HttpParams> defaultParams) { super(manager, defaultParams); } } @Singleton private static class SocketWrappingLimeHttpClientProvider extends AbstractLimeHttpClientProvider { @Inject public SocketWrappingLimeHttpClientProvider(@Named("socketWrappingConnectionManager") ReapingClientConnectionManager manager, @Named("defaults") Provider<HttpParams> defaultParams) { super(manager, defaultParams); } } @Singleton private static class SSLSchemeRegistryProvider extends AbstractLazySingletonProvider<SchemeRegistry> { @Inject public SSLSchemeRegistryProvider() { } @Override protected SchemeRegistry createObject() { try { SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(new Scheme("http", new PlainSocketFactory(), 80)); schemeRegistry.register(new Scheme("tls", new SSLSocketFactory(SSLContext.getDefault()),443)); schemeRegistry.register(new Scheme("https", new SSLSocketFactory(SSLContext.getDefault()),443)); return schemeRegistry; } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } } @Singleton private static class LimeSchemeRegistryProvider extends AbstractLazySingletonProvider<SchemeRegistry> { private final Provider<SocketsManager> socketsManager; @Inject public LimeSchemeRegistryProvider(Provider<SocketsManager> socketsManager) { this.socketsManager = socketsManager; } @Override protected SchemeRegistry createObject() { SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", new LimeSocketFactory(socketsManager, SocketsManager.ConnectType.PLAIN), 80)); registry.register(new Scheme("tls", new LimeSocketFactory(socketsManager, SocketsManager.ConnectType.TLS),80)); return registry; } } @Singleton private static class SocketWrappingSchemeRegistryProvider extends AbstractLazySingletonProvider<SchemeRegistry> { @Override protected SchemeRegistry createObject() { SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", new SocketWrapperProtocolSocketFactory(), 80)); registry.register(new Scheme("tls", new SocketWrapperProtocolSocketFactory(),80)); return registry; } } private abstract static class AbstractClientConnectionManagerProvider extends AbstractLazySingletonProvider<ReapingClientConnectionManager> { private final Provider<SchemeRegistry> registry; private final Provider<ScheduledExecutorService> scheduler; private final Provider<HttpParams> defaultParams; public AbstractClientConnectionManagerProvider(Provider<SchemeRegistry> registry, Provider<ScheduledExecutorService> scheduler, Provider<HttpParams> defaultParams) { this.registry = registry; this.scheduler = scheduler; this.defaultParams = defaultParams; } @Override public ReapingClientConnectionManager createObject() { return new ReapingClientConnectionManager(registry, scheduler, defaultParams); } } @Singleton private static class SSLClientConnectionManagerProvider extends AbstractClientConnectionManagerProvider { @Inject public SSLClientConnectionManagerProvider(@Named("sslSchemeRegistry")Provider<SchemeRegistry> registry, @Named("backgroundExecutor") Provider<ScheduledExecutorService> scheduler, @Named("defaults") Provider<HttpParams> defaultParams) { super(registry, scheduler, defaultParams); } } @Singleton private static class LimeClientConnectionManagerProvider extends AbstractClientConnectionManagerProvider { @Inject public LimeClientConnectionManagerProvider(@Named("limeSchemeRegistry")Provider<SchemeRegistry> registry, @Named("backgroundExecutor") Provider<ScheduledExecutorService> scheduler, @Named("defaults") Provider<HttpParams> defaultParams) { super(registry, scheduler, defaultParams); } } @Singleton private static class SocketWrappingClientConnectionManagerProvider extends AbstractClientConnectionManagerProvider { @Inject public SocketWrappingClientConnectionManagerProvider(@Named("socketWrappingSchemeRegistry")Provider<SchemeRegistry> registry, @Named("backgroundExecutor") Provider<ScheduledExecutorService> scheduler, @Named("defaults") Provider<HttpParams> defaultParams) { super(registry, scheduler, defaultParams); } } private static class LimeSocketFactory implements SocketFactory { final Provider<SocketsManager> socketsManager; final SocketsManager.ConnectType type; public LimeSocketFactory(Provider<SocketsManager> socketsManager, SocketsManager.ConnectType type) { this.socketsManager = socketsManager; this.type = type; } public Socket createSocket() throws IOException { return socketsManager.get().create(type); } public Socket connectSocket(Socket socket, String targetHost, int targetPort, InetAddress localAddress, int localPort, HttpParams httpParams) throws IOException, UnknownHostException, ConnectTimeoutException { if(socket == null) { socket = createSocket(); } InetSocketAddress localSocketAddr = null; if((localAddress != null && !localAddress.isAnyLocalAddress()) || localPort > 0) { localSocketAddr = new InetSocketAddress(localAddress, localPort); } return socketsManager.get().connect((NBSocket)socket, localSocketAddr, new InetSocketAddress(targetHost,targetPort), HttpConnectionParams.getConnectionTimeout(httpParams), type); } public boolean isSecure(Socket socket) throws IllegalArgumentException { return false; // TODO type.equals(SocketsManager.ConnectType.TLS); // TODO use socket instead? } } @Singleton private static class DefaultHttpParamsProvider implements Provider<HttpParams>{ /** * The amount of time to wait while trying to connect to a specified * host via TCP. If we exceed this value, an IOException is thrown * while trying to connect. */ private static final int CONNECTION_TIMEOUT = 5000; /** * The amount of time to wait while receiving data from a specified * host. Used as an SO_TIMEOUT. */ private static final int TIMEOUT = 8000; /** * The maximum number of times to allow redirects from hosts. */ private static final int MAXIMUM_REDIRECTS = 10; @Inject private DefaultHttpParamsProvider(){} public HttpParams get() { BasicHttpParams params = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT); HttpConnectionParams.setSoTimeout(params, TIMEOUT); HttpClientParams.setRedirecting(params, true); params.setIntParameter(ClientPNames.MAX_REDIRECTS, MAXIMUM_REDIRECTS); return params; } } }