Skip to content

Commit dc35bc5

Browse files
authored
feat: support websocket transport (#557)
1 parent 651aa29 commit dc35bc5

24 files changed

+5330
-6
lines changed

agentscope-core/src/main/java/io/agentscope/core/model/transport/HttpTransportConfig.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public class HttpTransportConfig {
4040
private final int maxIdleConnections;
4141
private final Duration keepAliveDuration;
4242
private final boolean ignoreSsl;
43+
private final ProxyConfig proxyConfig;
4344

4445
private HttpTransportConfig(Builder builder) {
4546
this.connectTimeout = builder.connectTimeout;
@@ -48,6 +49,7 @@ private HttpTransportConfig(Builder builder) {
4849
this.maxIdleConnections = builder.maxIdleConnections;
4950
this.keepAliveDuration = builder.keepAliveDuration;
5051
this.ignoreSsl = builder.ignoreSsl;
52+
this.proxyConfig = builder.proxyConfig;
5153
}
5254

5355
/**
@@ -108,6 +110,15 @@ public boolean isIgnoreSsl() {
108110
return ignoreSsl;
109111
}
110112

113+
/**
114+
* Get the proxy configuration.
115+
*
116+
* @return the proxy configuration, or null if no proxy is configured
117+
*/
118+
public ProxyConfig getProxyConfig() {
119+
return proxyConfig;
120+
}
121+
111122
/**
112123
* Create a new builder for HttpTransportConfig.
113124
*
@@ -136,6 +147,7 @@ public static class Builder {
136147
private int maxIdleConnections = 5;
137148
private Duration keepAliveDuration = Duration.ofMinutes(5);
138149
private boolean ignoreSsl = false;
150+
private ProxyConfig proxyConfig = null;
139151

140152
/**
141153
* Set the connect timeout.
@@ -207,6 +219,19 @@ public Builder ignoreSsl(boolean ignoreSsl) {
207219
return this;
208220
}
209221

222+
/**
223+
* Set the proxy configuration.
224+
*
225+
* <p>Supports HTTP and SOCKS proxies. See {@link ProxyConfig} for details.
226+
*
227+
* @param proxyConfig the proxy configuration
228+
* @return this builder
229+
*/
230+
public Builder proxy(ProxyConfig proxyConfig) {
231+
this.proxyConfig = proxyConfig;
232+
return this;
233+
}
234+
210235
/**
211236
* Build the HttpTransportConfig.
212237
*

agentscope-core/src/main/java/io/agentscope/core/model/transport/JdkHttpTransport.java

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
import java.io.IOException;
2020
import java.io.InputStream;
2121
import java.io.InputStreamReader;
22+
import java.net.InetSocketAddress;
23+
import java.net.PasswordAuthentication;
24+
import java.net.Proxy;
25+
import java.net.ProxySelector;
26+
import java.net.SocketAddress;
2227
import java.net.URI;
2328
import java.net.http.HttpClient;
2429
import java.net.http.HttpClient.Redirect;
@@ -29,6 +34,8 @@
2934
import java.security.NoSuchAlgorithmException;
3035
import java.security.SecureRandom;
3136
import java.security.cert.X509Certificate;
37+
import java.util.Collections;
38+
import java.util.List;
3239
import java.util.Map;
3340
import java.util.Objects;
3441
import java.util.concurrent.CompletableFuture;
@@ -114,14 +121,50 @@ private static HttpClient buildClient(HttpTransportConfig config) {
114121
sslContext.init(
115122
null, new TrustManager[] {new TrustAllManager()}, new SecureRandom());
116123
builder.sslContext(sslContext);
117-
log.warn(
118-
"SSL certificate verification is disabled. "
119-
+ "This should only be used for testing.");
124+
log.error(
125+
"SSL certificate verification has been disabled for this WebSocket client."
126+
+ " This configuration must only be used for local development or"
127+
+ " testing with self-signed certificates. Do not disable SSL"
128+
+ " verification in production environments, as it exposes connections"
129+
+ " to man-in-the-middle attacks.");
120130
} catch (NoSuchAlgorithmException | KeyManagementException e) {
121131
throw new HttpTransportException("Failed to create insecure SSL context", e);
122132
}
123133
}
124134

135+
// Configure proxy
136+
if (config.getProxyConfig() != null) {
137+
ProxyConfig proxyConfig = config.getProxyConfig();
138+
139+
if (proxyConfig.getNonProxyHosts() != null
140+
&& !proxyConfig.getNonProxyHosts().isEmpty()) {
141+
builder.proxy(new NonProxyHostsSelector(proxyConfig));
142+
} else {
143+
builder.proxy(
144+
ProxySelector.of(
145+
new InetSocketAddress(
146+
proxyConfig.getHost(), proxyConfig.getPort())));
147+
}
148+
149+
// Note: JDK HttpClient does not support SOCKS5 authentication directly.
150+
// For HTTP proxy authentication, use Authenticator.
151+
if (proxyConfig.hasAuthentication() && proxyConfig.getType() == ProxyType.HTTP) {
152+
final String username = proxyConfig.getUsername();
153+
final String password = proxyConfig.getPassword();
154+
builder.authenticator(
155+
new java.net.Authenticator() {
156+
@Override
157+
protected PasswordAuthentication getPasswordAuthentication() {
158+
if (getRequestorType() == RequestorType.PROXY) {
159+
return new PasswordAuthentication(
160+
username, password.toCharArray());
161+
}
162+
return null;
163+
}
164+
});
165+
}
166+
}
167+
125168
return builder.build();
126169
}
127170

@@ -424,4 +467,33 @@ public X509Certificate[] getAcceptedIssuers() {
424467
return new X509Certificate[0];
425468
}
426469
}
470+
471+
/**
472+
* ProxySelector that respects non-proxy hosts configuration.
473+
*/
474+
private static class NonProxyHostsSelector extends ProxySelector {
475+
private final ProxyConfig proxyConfig;
476+
private final List<Proxy> proxyList;
477+
478+
NonProxyHostsSelector(ProxyConfig proxyConfig) {
479+
this.proxyConfig = proxyConfig;
480+
this.proxyList = Collections.singletonList(proxyConfig.toJavaProxy());
481+
}
482+
483+
@Override
484+
public List<Proxy> select(URI uri) {
485+
if (uri == null || uri.getHost() == null) {
486+
return proxyList;
487+
}
488+
if (proxyConfig.shouldBypass(uri.getHost())) {
489+
return Collections.singletonList(Proxy.NO_PROXY);
490+
}
491+
return proxyList;
492+
}
493+
494+
@Override
495+
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
496+
log.warn("Proxy connection failed: uri={}, address={}", uri, sa, ioe);
497+
}
498+
}
427499
}

agentscope-core/src/main/java/io/agentscope/core/model/transport/OkHttpTransport.java

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,24 @@
1818
import java.io.BufferedReader;
1919
import java.io.IOException;
2020
import java.io.InputStreamReader;
21+
import java.net.Proxy;
22+
import java.net.ProxySelector;
23+
import java.net.SocketAddress;
24+
import java.net.URI;
2125
import java.nio.charset.StandardCharsets;
2226
import java.security.KeyManagementException;
2327
import java.security.NoSuchAlgorithmException;
2428
import java.security.SecureRandom;
2529
import java.security.cert.X509Certificate;
30+
import java.util.Collections;
31+
import java.util.List;
2632
import java.util.Map;
2733
import java.util.concurrent.TimeUnit;
2834
import javax.net.ssl.SSLContext;
2935
import javax.net.ssl.TrustManager;
3036
import javax.net.ssl.X509TrustManager;
3137
import okhttp3.ConnectionPool;
38+
import okhttp3.Credentials;
3239
import okhttp3.MediaType;
3340
import okhttp3.OkHttpClient;
3441
import okhttp3.Request;
@@ -108,15 +115,45 @@ private OkHttpClient buildClient(HttpTransportConfig config) {
108115

109116
// Configure SSL (optionally ignore certificate verification)
110117
if (config.isIgnoreSsl()) {
111-
log.warn(
112-
"SSL certificate verification is disabled. This is not recommended for"
113-
+ " production.");
118+
log.error(
119+
"SSL certificate verification has been disabled for this WebSocket client. This"
120+
+ " configuration must only be used for local development or testing with"
121+
+ " self-signed certificates. Do not disable SSL verification in production"
122+
+ " environments, as it exposes connections to man-in-the-middle attacks.");
114123
builder =
115124
builder.sslSocketFactory(
116125
createTrustAllSslSocketFactory(), createTrustAllTrustManager())
117126
.hostnameVerifier((hostname, session) -> true);
118127
}
119128

129+
// Configure proxy
130+
if (config.getProxyConfig() != null) {
131+
ProxyConfig proxyConfig = config.getProxyConfig();
132+
133+
if (proxyConfig.getNonProxyHosts() != null
134+
&& !proxyConfig.getNonProxyHosts().isEmpty()) {
135+
builder.proxySelector(new NonProxyHostsSelector(proxyConfig));
136+
} else {
137+
builder.proxy(proxyConfig.toJavaProxy());
138+
}
139+
140+
if (proxyConfig.hasAuthentication()) {
141+
final String username = proxyConfig.getUsername();
142+
final String password = proxyConfig.getPassword();
143+
builder.proxyAuthenticator(
144+
(route, response) -> {
145+
if (response.request().header("Proxy-Authorization") != null) {
146+
return null; // Avoid infinite retry
147+
}
148+
String credential = Credentials.basic(username, password);
149+
return response.request()
150+
.newBuilder()
151+
.header("Proxy-Authorization", credential)
152+
.build();
153+
});
154+
}
155+
}
156+
120157
return builder.build();
121158
}
122159

@@ -425,4 +462,33 @@ public OkHttpTransport build() {
425462
return new OkHttpTransport(config);
426463
}
427464
}
465+
466+
/**
467+
* ProxySelector that respects non-proxy hosts configuration.
468+
*/
469+
private static class NonProxyHostsSelector extends ProxySelector {
470+
private final ProxyConfig proxyConfig;
471+
private final List<Proxy> proxyList;
472+
473+
NonProxyHostsSelector(ProxyConfig proxyConfig) {
474+
this.proxyConfig = proxyConfig;
475+
this.proxyList = Collections.singletonList(proxyConfig.toJavaProxy());
476+
}
477+
478+
@Override
479+
public List<Proxy> select(URI uri) {
480+
if (uri == null || uri.getHost() == null) {
481+
return proxyList;
482+
}
483+
if (proxyConfig.shouldBypass(uri.getHost())) {
484+
return Collections.singletonList(Proxy.NO_PROXY);
485+
}
486+
return proxyList;
487+
}
488+
489+
@Override
490+
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
491+
log.warn("Proxy connection failed: uri={}, address={}", uri, sa, ioe);
492+
}
493+
}
428494
}

0 commit comments

Comments
 (0)