SocksProxyTest.java
/*
* Copyright (c) 2024 AsyncHttpClient Project. All rights reserved.
*
* Licensed 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.
*/
package org.asynchttpclient.proxy;
import io.github.artsok.RepeatedIfExceptionsTest;
import org.asynchttpclient.AbstractBasicTest;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.Response;
import org.asynchttpclient.testserver.SocksProxy;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.jupiter.api.Test;
import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static org.asynchttpclient.Dsl.asyncHttpClient;
import static org.asynchttpclient.Dsl.config;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* Tests for SOCKS proxy support with both HTTP and HTTPS.
*/
public class SocksProxyTest extends AbstractBasicTest {
@Override
public AbstractHandler configureHandler() throws Exception {
return new ProxyTest.ProxyHandler();
}
@RepeatedIfExceptionsTest(repeats = 5)
public void testSocks4ProxyWithHttp() throws Exception {
// Start SOCKS proxy in background thread
Thread socksProxyThread = new Thread(() -> {
try {
new SocksProxy(60000);
} catch (Exception e) {
logger.error("Failed to establish SocksProxy", e);
}
});
socksProxyThread.start();
// Give the proxy time to start
Thread.sleep(1000);
try (AsyncHttpClient client = asyncHttpClient()) {
String target = "http://localhost:" + port1 + '/';
Future<Response> f = client.prepareGet(target)
.setProxyServer(new ProxyServer.Builder("localhost", 8000).setProxyType(ProxyType.SOCKS_V4))
.execute();
Response response = f.get(60, TimeUnit.SECONDS);
assertNotNull(response);
assertEquals(200, response.getStatusCode());
}
}
@RepeatedIfExceptionsTest(repeats = 5)
public void testSocks5ProxyWithHttp() throws Exception {
// Start SOCKS proxy in background thread
Thread socksProxyThread = new Thread(() -> {
try {
new SocksProxy(60000);
} catch (Exception e) {
logger.error("Failed to establish SocksProxy", e);
}
});
socksProxyThread.start();
// Give the proxy time to start
Thread.sleep(1000);
try (AsyncHttpClient client = asyncHttpClient()) {
String target = "http://localhost:" + port1 + '/';
Future<Response> f = client.prepareGet(target)
.setProxyServer(new ProxyServer.Builder("localhost", 8000).setProxyType(ProxyType.SOCKS_V5))
.execute();
Response response = f.get(60, TimeUnit.SECONDS);
assertNotNull(response);
assertEquals(200, response.getStatusCode());
}
}
@Test
public void testSocks5ProxyWithHttpsDoesNotThrowException() throws Exception {
// This test specifically verifies that HTTPS requests through SOCKS5 proxy
// do not throw NoSuchElementException: socks anymore
// Start SOCKS proxy in background thread
Thread socksProxyThread = new Thread(() -> {
try {
new SocksProxy(10000); // shorter time for test
} catch (Exception e) {
logger.error("Failed to establish SocksProxy", e);
}
});
socksProxyThread.start();
// Give the proxy time to start
Thread.sleep(1000);
try (AsyncHttpClient client = asyncHttpClient(config()
.setProxyServer(new ProxyServer.Builder("localhost", 8000).setProxyType(ProxyType.SOCKS_V5))
.setConnectTimeout(Duration.ofMillis(5000))
.setRequestTimeout(Duration.ofMillis(10000)))) {
// This would previously throw: java.util.NoSuchElementException: socks
// We expect this to fail with connection timeout (since we don't have a real HTTPS target)
// but NOT with NoSuchElementException
try {
Future<Response> f = client.prepareGet("https://httpbin.org/get").execute();
f.get(8, TimeUnit.SECONDS);
// If we reach here, great! The SOCKS proxy worked
} catch (Exception e) {
// We should NOT see NoSuchElementException: socks anymore
String message = e.getMessage();
if (message != null && message.contains("socks") && message.contains("NoSuchElementException")) {
throw new AssertionError("NoSuchElementException: socks still occurs", e);
}
// Other exceptions like connection timeout are expected since we don't have a real working SOCKS proxy setup
logger.info("Expected exception (not the SOCKS handler bug): " + e.getClass().getSimpleName() + ": " + message);
}
}
}
@Test
public void testSocks4ProxyWithHttpsDoesNotThrowException() throws Exception {
// This test specifically verifies that HTTPS requests through SOCKS4 proxy
// do not throw NoSuchElementException: socks anymore
// Start SOCKS proxy in background thread
Thread socksProxyThread = new Thread(() -> {
try {
new SocksProxy(10000); // shorter time for test
} catch (Exception e) {
logger.error("Failed to establish SocksProxy", e);
}
});
socksProxyThread.start();
// Give the proxy time to start
Thread.sleep(1000);
try (AsyncHttpClient client = asyncHttpClient(config()
.setProxyServer(new ProxyServer.Builder("localhost", 8000).setProxyType(ProxyType.SOCKS_V4))
.setConnectTimeout(Duration.ofMillis(5000))
.setRequestTimeout(Duration.ofMillis(10000)))) {
// This would previously throw: java.util.NoSuchElementException: socks
// We expect this to fail with connection timeout (since we don't have a real HTTPS target)
// but NOT with NoSuchElementException
try {
Future<Response> f = client.prepareGet("https://httpbin.org/get").execute();
f.get(8, TimeUnit.SECONDS);
// If we reach here, great! The SOCKS proxy worked
} catch (Exception e) {
// We should NOT see NoSuchElementException: socks anymore
String message = e.getMessage();
if (message != null && message.contains("socks") && message.contains("NoSuchElementException")) {
throw new AssertionError("NoSuchElementException: socks still occurs", e);
}
// Other exceptions like connection timeout are expected since we don't have a real working SOCKS proxy setup
logger.info("Expected exception (not the SOCKS handler bug): " + e.getClass().getSimpleName() + ": " + message);
}
}
}
@Test
public void testIssue1913NoSuchElementExceptionSocks5() throws Exception {
// Reproduces the exact issue from GitHub issue #1913 with SOCKS5
// This uses the exact code pattern from the issue report
var proxyServer = new ProxyServer.Builder("127.0.0.1", 1081)
.setProxyType(ProxyType.SOCKS_V5);
try (var client = asyncHttpClient(config()
.setProxyServer(proxyServer.build())
.setConnectTimeout(Duration.ofMillis(2000))
.setRequestTimeout(Duration.ofMillis(5000)))) {
// This would previously throw: java.util.NoSuchElementException: socks
// We expect this to fail with connection timeout (since proxy doesn't exist)
// but NOT with NoSuchElementException
try {
var response = client.prepareGet("https://cloudflare.com/cdn-cgi/trace").execute().get();
// If we reach here, great! The fix worked and proxy connection succeeded
logger.info("Connection successful: " + response.getStatusCode());
} catch (Exception e) {
// Check that we don't get the NoSuchElementException: socks anymore
Throwable cause = e.getCause();
String message = cause != null ? cause.getMessage() : e.getMessage();
// This should NOT contain the original error
if (message != null && message.contains("socks") &&
(e.toString().contains("NoSuchElementException") || cause != null && cause.toString().contains("NoSuchElementException"))) {
throw new AssertionError("NoSuchElementException: socks still occurs - fix didn't work: " + e.toString());
}
// Other exceptions like connection timeout are expected since we don't have a working SOCKS proxy
logger.info("Expected exception (not the SOCKS handler bug): " + e.getClass().getSimpleName() + ": " + message);
}
}
}
@Test
public void testIssue1913NoSuchElementExceptionSocks4() throws Exception {
// Reproduces the exact issue from GitHub issue #1913 with SOCKS4
// This uses the exact code pattern from the issue report
var proxyServer = new ProxyServer.Builder("127.0.0.1", 1081)
.setProxyType(ProxyType.SOCKS_V4);
try (var client = asyncHttpClient(config()
.setProxyServer(proxyServer.build())
.setConnectTimeout(Duration.ofMillis(2000))
.setRequestTimeout(Duration.ofMillis(5000)))) {
try {
var response = client.prepareGet("https://cloudflare.com/cdn-cgi/trace").execute().get();
logger.info("Connection successful: " + response.getStatusCode());
} catch (Exception e) {
// Check that we don't get the NoSuchElementException: socks anymore
Throwable cause = e.getCause();
String message = cause != null ? cause.getMessage() : e.getMessage();
if (message != null && message.contains("socks") &&
(e.toString().contains("NoSuchElementException") || cause != null && cause.toString().contains("NoSuchElementException"))) {
throw new AssertionError("NoSuchElementException: socks still occurs - fix didn't work: " + e.toString());
}
logger.info("Expected exception (not the SOCKS handler bug): " + e.getClass().getSimpleName() + ": " + message);
}
}
}
}