Skip to content

Commit

Permalink
Experimental SOCKS4a proxy support added, checkstyle changed, v4.2.5
Browse files Browse the repository at this point in the history
  • Loading branch information
sitfoxfly committed Nov 20, 2019
1 parent a2ff673 commit 6c9ac77
Show file tree
Hide file tree
Showing 8 changed files with 624 additions and 8 deletions.
18 changes: 13 additions & 5 deletions checkstyle.xml
Expand Up @@ -99,9 +99,15 @@


<!-- Checks for Javadoc comments. --> <!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html --> <!-- See http://checkstyle.sf.net/config_javadoc.html -->
<module name="JavadocMethod"/> <module name="JavadocMethod">
<module name="JavadocType"/> <property name="scope" value="public"/>
<module name="JavadocVariable"/> </module>
<module name="JavadocType">
<property name="scope" value="public"/>
</module>
<module name="JavadocVariable">
<property name="scope" value="public"/>
</module>
<module name="JavadocStyle"/> <module name="JavadocStyle"/>


<!-- Checks for Naming Conventions. --> <!-- Checks for Naming Conventions. -->
Expand Down Expand Up @@ -176,7 +182,9 @@


<!-- Checks for class design --> <!-- Checks for class design -->
<!-- See http://checkstyle.sf.net/config_design.html --> <!-- See http://checkstyle.sf.net/config_design.html -->
<module name="DesignForExtension"/> <module name="DesignForExtension">
<property name="ignoredAnnotations" value="Override, Test"/>
</module>
<module name="FinalClass"/> <module name="FinalClass"/>
<module name="HideUtilityClassConstructor"/> <module name="HideUtilityClassConstructor"/>
<module name="InterfaceIsType"/> <module name="InterfaceIsType"/>
Expand All @@ -185,7 +193,7 @@
<!-- Miscellaneous other checks. --> <!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sf.net/config_misc.html --> <!-- See http://checkstyle.sf.net/config_misc.html -->
<module name="ArrayTypeStyle"/> <module name="ArrayTypeStyle"/>
<module name="FinalParameters"/> <!-- <module name="FinalParameters"/> -->
<module name="TodoComment"/> <module name="TodoComment"/>
<module name="UpperEll"/> <module name="UpperEll"/>


Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -10,7 +10,7 @@


<groupId>ai.preferred</groupId> <groupId>ai.preferred</groupId>
<artifactId>venom</artifactId> <artifactId>venom</artifactId>
<version>4.2.4</version> <version>4.2.5</version>
<packaging>jar</packaging> <packaging>jar</packaging>


<name>${project.groupId}:${project.artifactId}</name> <name>${project.groupId}:${project.artifactId}</name>
Expand Down
49 changes: 47 additions & 2 deletions src/main/java/ai/preferred/venom/fetcher/AsyncFetcher.java
Expand Up @@ -21,6 +21,9 @@
import ai.preferred.venom.request.HttpFetcherRequest; import ai.preferred.venom.request.HttpFetcherRequest;
import ai.preferred.venom.request.Request; import ai.preferred.venom.request.Request;
import ai.preferred.venom.response.Response; import ai.preferred.venom.response.Response;
import ai.preferred.venom.socks.SocksConnectingIOReactor;
import ai.preferred.venom.socks.SocksHttpRoutePlanner;
import ai.preferred.venom.socks.SocksIOSessionStrategy;
import ai.preferred.venom.storage.FileManager; import ai.preferred.venom.storage.FileManager;
import ai.preferred.venom.uagent.DefaultUserAgent; import ai.preferred.venom.uagent.DefaultUserAgent;
import ai.preferred.venom.uagent.UserAgent; import ai.preferred.venom.uagent.UserAgent;
Expand All @@ -42,11 +45,20 @@
import org.apache.http.client.utils.URIUtils; import org.apache.http.client.utils.URIUtils;
import org.apache.http.concurrent.BasicFuture; import org.apache.http.concurrent.BasicFuture;
import org.apache.http.concurrent.FutureCallback; import org.apache.http.concurrent.FutureCallback;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.conn.DefaultRoutePlanner;
import org.apache.http.impl.conn.DefaultSchemePortResolver;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.IOReactorConfig; import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.client.methods.HttpAsyncMethods; import org.apache.http.nio.client.methods.HttpAsyncMethods;
import org.apache.http.nio.conn.NoopIOSessionStrategy;
import org.apache.http.nio.conn.SchemeIOSessionStrategy;
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
import org.apache.http.nio.reactor.IOReactorException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;


Expand Down Expand Up @@ -186,13 +198,33 @@ private AsyncFetcher(final Builder builder) {
.build(); .build();


final HttpAsyncClientBuilder clientBuilder = HttpAsyncClientBuilder.create() final HttpAsyncClientBuilder clientBuilder = HttpAsyncClientBuilder.create()
.setDefaultIOReactorConfig(reactorConfig)
.setThreadFactory(builder.threadFactory)
.setMaxConnPerRoute(builder.maxRouteConnections) .setMaxConnPerRoute(builder.maxRouteConnections)
.setMaxConnTotal(builder.maxConnections) .setMaxConnTotal(builder.maxConnections)
.setSSLContext(builder.sslContext) .setSSLContext(builder.sslContext)
.setRedirectStrategy(builder.redirectStrategy); .setRedirectStrategy(builder.redirectStrategy);


if (builder.enableSocksProxy) {
final PoolingNHttpClientConnectionManager connectionManager;
try {
final SSLIOSessionStrategy sslioSessionStrategy = SSLIOSessionStrategy.getDefaultStrategy();
final Registry<SchemeIOSessionStrategy> reg = RegistryBuilder.<SchemeIOSessionStrategy>create()
.register("socks", new SocksIOSessionStrategy(sslioSessionStrategy))
.register("http", NoopIOSessionStrategy.INSTANCE)
.register("https", sslioSessionStrategy)
.build();

final SocksConnectingIOReactor reactor = new SocksConnectingIOReactor(reactorConfig, builder.threadFactory);
connectionManager = new PoolingNHttpClientConnectionManager(reactor, reg);
clientBuilder.setConnectionManager(connectionManager)
.setRoutePlanner(new SocksHttpRoutePlanner(new DefaultRoutePlanner(DefaultSchemePortResolver.INSTANCE)));
} catch (IOReactorException e) {
LOGGER.error("Disabling SOCKS protocol", e);
clientBuilder.setDefaultIOReactorConfig(reactorConfig).setThreadFactory(builder.threadFactory);
}
} else {
clientBuilder.setDefaultIOReactorConfig(reactorConfig).setThreadFactory(builder.threadFactory);
}

if (builder.maxConnections < builder.maxRouteConnections) { if (builder.maxConnections < builder.maxRouteConnections) {
clientBuilder.setMaxConnTotal(builder.maxRouteConnections); clientBuilder.setMaxConnTotal(builder.maxRouteConnections);
LOGGER.info("Maximum total connections will be set to {}, to match maximum route connection.", LOGGER.info("Maximum total connections will be set to {}, to match maximum route connection.",
Expand Down Expand Up @@ -442,6 +474,8 @@ public static final class Builder {
*/ */
private final List<Callback> callbacks; private final List<Callback> callbacks;


private boolean enableSocksProxy;

/** /**
* Determines whether cookie storage is allowed. * Determines whether cookie storage is allowed.
*/ */
Expand Down Expand Up @@ -555,6 +589,17 @@ private Builder() {
connectTimeout = -1; connectTimeout = -1;
socketTimeout = -1; socketTimeout = -1;
compressed = true; compressed = true;
enableSocksProxy = false;
}

/**
* Enables SOCKS protocol for proxies (socks://). Experimental.
*
* @return this
*/
public Builder enableSocksProxy() {
enableSocksProxy = true;
return this;
} }


/** /**
Expand Down
@@ -0,0 +1,53 @@
package ai.preferred.venom.socks;

import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.reactor.IOEventDispatch;
import org.apache.http.nio.reactor.IOReactorException;

import java.io.InterruptedIOException;
import java.util.concurrent.ThreadFactory;

/**
* This IOReactor makes sure that the supplied {@link IOEventDispatch} is decorated with {@link SocksIOEventDispatch}.
*/
public class SocksConnectingIOReactor extends DefaultConnectingIOReactor {

/**
* Creates an instance of SocksConnectingIOReactor with the given configuration.
*
* @param config I/O reactor configuration.
* @param threadFactory the factory to create threads.
* Can be {@code null}.
* @throws IOReactorException in case if a non-recoverable I/O error.
*/
public SocksConnectingIOReactor(IOReactorConfig config, ThreadFactory threadFactory) throws IOReactorException {
super(config, threadFactory);
}

/**
* Creates an instance of SocksConnectingIOReactor with the given configuration.
*
* @param config I/O reactor configuration.
* Can be {@code null}.
* @throws IOReactorException in case if a non-recoverable I/O error.
*/
public SocksConnectingIOReactor(IOReactorConfig config) throws IOReactorException {
super(config);
}

/**
* Creates an instance of SocksConnectingIOReactor with default configuration.
*
* @throws IOReactorException in case if a non-recoverable I/O error.
*/
public SocksConnectingIOReactor() throws IOReactorException {
super();
}

@Override
public void execute(final IOEventDispatch eventDispatch) throws InterruptedIOException, IOReactorException {
super.execute(new SocksIOEventDispatch(eventDispatch));
}

}
40 changes: 40 additions & 0 deletions src/main/java/ai/preferred/venom/socks/SocksHttpRoutePlanner.java
@@ -0,0 +1,40 @@
package ai.preferred.venom.socks;

import com.google.common.annotations.Beta;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.routing.HttpRoutePlanner;
import org.apache.http.protocol.HttpContext;

/**
* This route planners ensures that the connection to https server via socks proxy works. It prevents http client from
* tunnelling the IO session twice ({@link SocksIOSessionStrategy} upgrades {@link SocksIOSession} to
* {@link org.apache.http.nio.reactor.ssl.SSLIOSession} when necessary).
*/
@Beta
public class SocksHttpRoutePlanner implements HttpRoutePlanner {

private final HttpRoutePlanner rp;

/**
* Decorates {@link HttpRoutePlanner}.
*
* @param rp decorated route planner
*/
public SocksHttpRoutePlanner(final HttpRoutePlanner rp) {
this.rp = rp;
}

@Override
public HttpRoute determineRoute(HttpHost host, HttpRequest request, HttpContext context) throws HttpException {
final HttpRoute route = rp.determineRoute(host, request, context);
final boolean secure = "https".equalsIgnoreCase(route.getTargetHost().getSchemeName());
if (secure && route.getProxyHost() != null && "socks".equalsIgnoreCase(route.getProxyHost().getSchemeName())) {
return new HttpRoute(route.getTargetHost(), route.getLocalAddress(), route.getProxyHost(), false);
}
return route;
}

}
118 changes: 118 additions & 0 deletions src/main/java/ai/preferred/venom/socks/SocksIOEventDispatch.java
@@ -0,0 +1,118 @@
package ai.preferred.venom.socks;

import org.apache.http.nio.NHttpClientConnection;
import org.apache.http.nio.NHttpClientEventHandler;
import org.apache.http.nio.NHttpConnection;
import org.apache.http.nio.protocol.HttpAsyncRequestExecutor;
import org.apache.http.nio.reactor.IOEventDispatch;
import org.apache.http.nio.reactor.IOSession;

import java.io.IOException;

/**
* This class wraps and handles IO dispatch related to {@link SocksIOSession}.
*/
public class SocksIOEventDispatch implements IOEventDispatch {

private final IOEventDispatch dispatch;

/**
* Decorates {@link IOEventDispatch}.
*
* @param dispatch delegated IO dispatch
*/
public SocksIOEventDispatch(IOEventDispatch dispatch) {
this.dispatch = dispatch;
}

@Override
public void connected(IOSession session) {
dispatch.connected(session);
}

@Override
public void inputReady(IOSession session) {
try {
if (initializeSocksSession(session)) {
dispatch.inputReady(session);
}
} catch (RuntimeException e) {
session.shutdown();
throw e;
}
}

@Override
public void outputReady(IOSession session) {
try {
if (initializeSocksSession(session)) {
dispatch.outputReady(session);
}
} catch (RuntimeException e) {
session.shutdown();
throw e;
}
}

@Override
public void timeout(IOSession session) {
try {
dispatch.timeout(session);
final SocksIOSession socksIOSession = getSocksSession(session);
if (socksIOSession != null) {
socksIOSession.shutdown();
}
} catch (RuntimeException e) {
session.shutdown();
throw e;
}
}

@Override
public void disconnected(IOSession session) {
dispatch.disconnected(session);
}

private boolean initializeSocksSession(IOSession session) {
final SocksIOSession socksSession = getSocksSession(session);
if (socksSession != null) {
try {
try {
if (!socksSession.isInitialized()) {
return socksSession.initialize();
}
} catch (final IOException e) {
onException(socksSession, e);
throw new RuntimeException(e);
}
} catch (final RuntimeException e) {
socksSession.shutdown();
throw e;
}
}
return true;
}

private void onException(IOSession session, Exception ex) {
final NHttpClientConnection conn = getConnection(session);
if (conn != null) {
final NHttpClientEventHandler handler = getEventHandler(conn);
if (handler != null) {
handler.exception(conn, ex);
}
}
}

private SocksIOSession getSocksSession(IOSession session) {
return (SocksIOSession) session.getAttribute(SocksIOSession.SESSION_KEY);
}

private NHttpClientConnection getConnection(IOSession session) {
return (NHttpClientConnection) session.getAttribute(IOEventDispatch.CONNECTION_KEY);
}

private NHttpClientEventHandler getEventHandler(NHttpConnection conn) {
return (NHttpClientEventHandler) conn.getContext().getAttribute(HttpAsyncRequestExecutor.HTTP_HANDLER);
}

}

0 comments on commit 6c9ac77

Please sign in to comment.