Skip to content

Commit 6c9ac77

Browse files
committed
Experimental SOCKS4a proxy support added, checkstyle changed, v4.2.5
1 parent a2ff673 commit 6c9ac77

File tree

8 files changed

+624
-8
lines changed

8 files changed

+624
-8
lines changed

checkstyle.xml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,15 @@
9999

100100
<!-- Checks for Javadoc comments. -->
101101
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
102-
<module name="JavadocMethod"/>
103-
<module name="JavadocType"/>
104-
<module name="JavadocVariable"/>
102+
<module name="JavadocMethod">
103+
<property name="scope" value="public"/>
104+
</module>
105+
<module name="JavadocType">
106+
<property name="scope" value="public"/>
107+
</module>
108+
<module name="JavadocVariable">
109+
<property name="scope" value="public"/>
110+
</module>
105111
<module name="JavadocStyle"/>
106112

107113
<!-- Checks for Naming Conventions. -->
@@ -176,7 +182,9 @@
176182

177183
<!-- Checks for class design -->
178184
<!-- See http://checkstyle.sf.net/config_design.html -->
179-
<module name="DesignForExtension"/>
185+
<module name="DesignForExtension">
186+
<property name="ignoredAnnotations" value="Override, Test"/>
187+
</module>
180188
<module name="FinalClass"/>
181189
<module name="HideUtilityClassConstructor"/>
182190
<module name="InterfaceIsType"/>
@@ -185,7 +193,7 @@
185193
<!-- Miscellaneous other checks. -->
186194
<!-- See http://checkstyle.sf.net/config_misc.html -->
187195
<module name="ArrayTypeStyle"/>
188-
<module name="FinalParameters"/>
196+
<!-- <module name="FinalParameters"/> -->
189197
<module name="TodoComment"/>
190198
<module name="UpperEll"/>
191199

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
<groupId>ai.preferred</groupId>
1212
<artifactId>venom</artifactId>
13-
<version>4.2.4</version>
13+
<version>4.2.5</version>
1414
<packaging>jar</packaging>
1515

1616
<name>${project.groupId}:${project.artifactId}</name>

src/main/java/ai/preferred/venom/fetcher/AsyncFetcher.java

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
import ai.preferred.venom.request.HttpFetcherRequest;
2222
import ai.preferred.venom.request.Request;
2323
import ai.preferred.venom.response.Response;
24+
import ai.preferred.venom.socks.SocksConnectingIOReactor;
25+
import ai.preferred.venom.socks.SocksHttpRoutePlanner;
26+
import ai.preferred.venom.socks.SocksIOSessionStrategy;
2427
import ai.preferred.venom.storage.FileManager;
2528
import ai.preferred.venom.uagent.DefaultUserAgent;
2629
import ai.preferred.venom.uagent.UserAgent;
@@ -42,11 +45,20 @@
4245
import org.apache.http.client.utils.URIUtils;
4346
import org.apache.http.concurrent.BasicFuture;
4447
import org.apache.http.concurrent.FutureCallback;
48+
import org.apache.http.config.Registry;
49+
import org.apache.http.config.RegistryBuilder;
4550
import org.apache.http.entity.ByteArrayEntity;
51+
import org.apache.http.impl.conn.DefaultRoutePlanner;
52+
import org.apache.http.impl.conn.DefaultSchemePortResolver;
4653
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
4754
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
55+
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
4856
import org.apache.http.impl.nio.reactor.IOReactorConfig;
4957
import org.apache.http.nio.client.methods.HttpAsyncMethods;
58+
import org.apache.http.nio.conn.NoopIOSessionStrategy;
59+
import org.apache.http.nio.conn.SchemeIOSessionStrategy;
60+
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
61+
import org.apache.http.nio.reactor.IOReactorException;
5062
import org.slf4j.Logger;
5163
import org.slf4j.LoggerFactory;
5264

@@ -186,13 +198,33 @@ private AsyncFetcher(final Builder builder) {
186198
.build();
187199

188200
final HttpAsyncClientBuilder clientBuilder = HttpAsyncClientBuilder.create()
189-
.setDefaultIOReactorConfig(reactorConfig)
190-
.setThreadFactory(builder.threadFactory)
191201
.setMaxConnPerRoute(builder.maxRouteConnections)
192202
.setMaxConnTotal(builder.maxConnections)
193203
.setSSLContext(builder.sslContext)
194204
.setRedirectStrategy(builder.redirectStrategy);
195205

206+
if (builder.enableSocksProxy) {
207+
final PoolingNHttpClientConnectionManager connectionManager;
208+
try {
209+
final SSLIOSessionStrategy sslioSessionStrategy = SSLIOSessionStrategy.getDefaultStrategy();
210+
final Registry<SchemeIOSessionStrategy> reg = RegistryBuilder.<SchemeIOSessionStrategy>create()
211+
.register("socks", new SocksIOSessionStrategy(sslioSessionStrategy))
212+
.register("http", NoopIOSessionStrategy.INSTANCE)
213+
.register("https", sslioSessionStrategy)
214+
.build();
215+
216+
final SocksConnectingIOReactor reactor = new SocksConnectingIOReactor(reactorConfig, builder.threadFactory);
217+
connectionManager = new PoolingNHttpClientConnectionManager(reactor, reg);
218+
clientBuilder.setConnectionManager(connectionManager)
219+
.setRoutePlanner(new SocksHttpRoutePlanner(new DefaultRoutePlanner(DefaultSchemePortResolver.INSTANCE)));
220+
} catch (IOReactorException e) {
221+
LOGGER.error("Disabling SOCKS protocol", e);
222+
clientBuilder.setDefaultIOReactorConfig(reactorConfig).setThreadFactory(builder.threadFactory);
223+
}
224+
} else {
225+
clientBuilder.setDefaultIOReactorConfig(reactorConfig).setThreadFactory(builder.threadFactory);
226+
}
227+
196228
if (builder.maxConnections < builder.maxRouteConnections) {
197229
clientBuilder.setMaxConnTotal(builder.maxRouteConnections);
198230
LOGGER.info("Maximum total connections will be set to {}, to match maximum route connection.",
@@ -442,6 +474,8 @@ public static final class Builder {
442474
*/
443475
private final List<Callback> callbacks;
444476

477+
private boolean enableSocksProxy;
478+
445479
/**
446480
* Determines whether cookie storage is allowed.
447481
*/
@@ -555,6 +589,17 @@ private Builder() {
555589
connectTimeout = -1;
556590
socketTimeout = -1;
557591
compressed = true;
592+
enableSocksProxy = false;
593+
}
594+
595+
/**
596+
* Enables SOCKS protocol for proxies (socks://). Experimental.
597+
*
598+
* @return this
599+
*/
600+
public Builder enableSocksProxy() {
601+
enableSocksProxy = true;
602+
return this;
558603
}
559604

560605
/**
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package ai.preferred.venom.socks;
2+
3+
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
4+
import org.apache.http.impl.nio.reactor.IOReactorConfig;
5+
import org.apache.http.nio.reactor.IOEventDispatch;
6+
import org.apache.http.nio.reactor.IOReactorException;
7+
8+
import java.io.InterruptedIOException;
9+
import java.util.concurrent.ThreadFactory;
10+
11+
/**
12+
* This IOReactor makes sure that the supplied {@link IOEventDispatch} is decorated with {@link SocksIOEventDispatch}.
13+
*/
14+
public class SocksConnectingIOReactor extends DefaultConnectingIOReactor {
15+
16+
/**
17+
* Creates an instance of SocksConnectingIOReactor with the given configuration.
18+
*
19+
* @param config I/O reactor configuration.
20+
* @param threadFactory the factory to create threads.
21+
* Can be {@code null}.
22+
* @throws IOReactorException in case if a non-recoverable I/O error.
23+
*/
24+
public SocksConnectingIOReactor(IOReactorConfig config, ThreadFactory threadFactory) throws IOReactorException {
25+
super(config, threadFactory);
26+
}
27+
28+
/**
29+
* Creates an instance of SocksConnectingIOReactor with the given configuration.
30+
*
31+
* @param config I/O reactor configuration.
32+
* Can be {@code null}.
33+
* @throws IOReactorException in case if a non-recoverable I/O error.
34+
*/
35+
public SocksConnectingIOReactor(IOReactorConfig config) throws IOReactorException {
36+
super(config);
37+
}
38+
39+
/**
40+
* Creates an instance of SocksConnectingIOReactor with default configuration.
41+
*
42+
* @throws IOReactorException in case if a non-recoverable I/O error.
43+
*/
44+
public SocksConnectingIOReactor() throws IOReactorException {
45+
super();
46+
}
47+
48+
@Override
49+
public void execute(final IOEventDispatch eventDispatch) throws InterruptedIOException, IOReactorException {
50+
super.execute(new SocksIOEventDispatch(eventDispatch));
51+
}
52+
53+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package ai.preferred.venom.socks;
2+
3+
import com.google.common.annotations.Beta;
4+
import org.apache.http.HttpException;
5+
import org.apache.http.HttpHost;
6+
import org.apache.http.HttpRequest;
7+
import org.apache.http.conn.routing.HttpRoute;
8+
import org.apache.http.conn.routing.HttpRoutePlanner;
9+
import org.apache.http.protocol.HttpContext;
10+
11+
/**
12+
* This route planners ensures that the connection to https server via socks proxy works. It prevents http client from
13+
* tunnelling the IO session twice ({@link SocksIOSessionStrategy} upgrades {@link SocksIOSession} to
14+
* {@link org.apache.http.nio.reactor.ssl.SSLIOSession} when necessary).
15+
*/
16+
@Beta
17+
public class SocksHttpRoutePlanner implements HttpRoutePlanner {
18+
19+
private final HttpRoutePlanner rp;
20+
21+
/**
22+
* Decorates {@link HttpRoutePlanner}.
23+
*
24+
* @param rp decorated route planner
25+
*/
26+
public SocksHttpRoutePlanner(final HttpRoutePlanner rp) {
27+
this.rp = rp;
28+
}
29+
30+
@Override
31+
public HttpRoute determineRoute(HttpHost host, HttpRequest request, HttpContext context) throws HttpException {
32+
final HttpRoute route = rp.determineRoute(host, request, context);
33+
final boolean secure = "https".equalsIgnoreCase(route.getTargetHost().getSchemeName());
34+
if (secure && route.getProxyHost() != null && "socks".equalsIgnoreCase(route.getProxyHost().getSchemeName())) {
35+
return new HttpRoute(route.getTargetHost(), route.getLocalAddress(), route.getProxyHost(), false);
36+
}
37+
return route;
38+
}
39+
40+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package ai.preferred.venom.socks;
2+
3+
import org.apache.http.nio.NHttpClientConnection;
4+
import org.apache.http.nio.NHttpClientEventHandler;
5+
import org.apache.http.nio.NHttpConnection;
6+
import org.apache.http.nio.protocol.HttpAsyncRequestExecutor;
7+
import org.apache.http.nio.reactor.IOEventDispatch;
8+
import org.apache.http.nio.reactor.IOSession;
9+
10+
import java.io.IOException;
11+
12+
/**
13+
* This class wraps and handles IO dispatch related to {@link SocksIOSession}.
14+
*/
15+
public class SocksIOEventDispatch implements IOEventDispatch {
16+
17+
private final IOEventDispatch dispatch;
18+
19+
/**
20+
* Decorates {@link IOEventDispatch}.
21+
*
22+
* @param dispatch delegated IO dispatch
23+
*/
24+
public SocksIOEventDispatch(IOEventDispatch dispatch) {
25+
this.dispatch = dispatch;
26+
}
27+
28+
@Override
29+
public void connected(IOSession session) {
30+
dispatch.connected(session);
31+
}
32+
33+
@Override
34+
public void inputReady(IOSession session) {
35+
try {
36+
if (initializeSocksSession(session)) {
37+
dispatch.inputReady(session);
38+
}
39+
} catch (RuntimeException e) {
40+
session.shutdown();
41+
throw e;
42+
}
43+
}
44+
45+
@Override
46+
public void outputReady(IOSession session) {
47+
try {
48+
if (initializeSocksSession(session)) {
49+
dispatch.outputReady(session);
50+
}
51+
} catch (RuntimeException e) {
52+
session.shutdown();
53+
throw e;
54+
}
55+
}
56+
57+
@Override
58+
public void timeout(IOSession session) {
59+
try {
60+
dispatch.timeout(session);
61+
final SocksIOSession socksIOSession = getSocksSession(session);
62+
if (socksIOSession != null) {
63+
socksIOSession.shutdown();
64+
}
65+
} catch (RuntimeException e) {
66+
session.shutdown();
67+
throw e;
68+
}
69+
}
70+
71+
@Override
72+
public void disconnected(IOSession session) {
73+
dispatch.disconnected(session);
74+
}
75+
76+
private boolean initializeSocksSession(IOSession session) {
77+
final SocksIOSession socksSession = getSocksSession(session);
78+
if (socksSession != null) {
79+
try {
80+
try {
81+
if (!socksSession.isInitialized()) {
82+
return socksSession.initialize();
83+
}
84+
} catch (final IOException e) {
85+
onException(socksSession, e);
86+
throw new RuntimeException(e);
87+
}
88+
} catch (final RuntimeException e) {
89+
socksSession.shutdown();
90+
throw e;
91+
}
92+
}
93+
return true;
94+
}
95+
96+
private void onException(IOSession session, Exception ex) {
97+
final NHttpClientConnection conn = getConnection(session);
98+
if (conn != null) {
99+
final NHttpClientEventHandler handler = getEventHandler(conn);
100+
if (handler != null) {
101+
handler.exception(conn, ex);
102+
}
103+
}
104+
}
105+
106+
private SocksIOSession getSocksSession(IOSession session) {
107+
return (SocksIOSession) session.getAttribute(SocksIOSession.SESSION_KEY);
108+
}
109+
110+
private NHttpClientConnection getConnection(IOSession session) {
111+
return (NHttpClientConnection) session.getAttribute(IOEventDispatch.CONNECTION_KEY);
112+
}
113+
114+
private NHttpClientEventHandler getEventHandler(NHttpConnection conn) {
115+
return (NHttpClientEventHandler) conn.getContext().getAttribute(HttpAsyncRequestExecutor.HTTP_HANDLER);
116+
}
117+
118+
}

0 commit comments

Comments
 (0)