This is an automated email from the ASF dual-hosted git repository.
hgruszecki pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/master by this push:
new e03355b58 feat(sdk): support hostnames in QUIC and WebSocket clients
(#2768)
e03355b58 is described below
commit e03355b58deeec63a7526c2dbc3c5cb5ac954534
Author: xin <[email protected]>
AuthorDate: Thu Feb 19 21:44:38 2026 +0900
feat(sdk): support hostnames in QUIC and WebSocket clients (#2768)
Closes #2729.
---------
Signed-off-by: shin <[email protected]>
Signed-off-by: seokjin0414 <[email protected]>
---
core/bench/src/args/examples.rs | 6 ++-
core/common/src/types/args/mod.rs | 16 ++----
core/sdk/src/quic/quic_client.rs | 81 ++++++++++++++++++++++++------
core/sdk/src/websocket/websocket_client.rs | 28 +++++++++--
4 files changed, 98 insertions(+), 33 deletions(-)
diff --git a/core/bench/src/args/examples.rs b/core/bench/src/args/examples.rs
index 40c63ced3..ddf888dbc 100644
--- a/core/bench/src/args/examples.rs
+++ b/core/bench/src/args/examples.rs
@@ -118,11 +118,15 @@ const EXAMPLES: &str = r#"EXAMPLES:
5) Remote Server Benchmarking:
- To benchmark a remote server, specify the server address in the transport
subcommand:
+ To benchmark a remote server, specify the server address in the transport
subcommand.
+ Both IP addresses and hostnames are supported:
$ cargo r -r --bin iggy-bench -- pinned-producer \
--streams 5 --producers 5 \
tcp --server-address 192.168.1.100:8090
+ $ cargo r -r --bin iggy-bench -- pinned-producer \
+ --streams 5 --producers 5 \
+ tcp --server-address localhost:8090
With custom credentials:
diff --git a/core/common/src/types/args/mod.rs
b/core/common/src/types/args/mod.rs
index cc22f9439..1315cba2c 100644
--- a/core/common/src/types/args/mod.rs
+++ b/core/common/src/types/args/mod.rs
@@ -362,18 +362,10 @@ const WEBSOCKET_TRANSPORT: &str = "websocket";
impl Args {
pub fn get_server_address(&self) -> Option<String> {
match self.transport.as_str() {
- QUIC_TRANSPORT =>
Some(self.quic_server_address.replace("localhost", "127.0.0.1")),
- HTTP_TRANSPORT => Some(
- self.http_api_url
- .clone()
- .replace("http://", "")
- .replace("localhost", "127.0.0.1"),
- ),
- TCP_TRANSPORT => Some(self.tcp_server_address.replace("localhost",
"127.0.0.1")),
- WEBSOCKET_TRANSPORT => Some(
- self.websocket_server_address
- .replace("localhost", "127.0.0.1"),
- ),
+ QUIC_TRANSPORT => Some(self.quic_server_address.clone()),
+ HTTP_TRANSPORT =>
Some(self.http_api_url.clone().replace("http://", "")),
+ TCP_TRANSPORT => Some(self.tcp_server_address.clone()),
+ WEBSOCKET_TRANSPORT => Some(self.websocket_server_address.clone()),
_ => None,
}
}
diff --git a/core/sdk/src/quic/quic_client.rs b/core/sdk/src/quic/quic_client.rs
index e55980d16..9ca335765 100644
--- a/core/sdk/src/quic/quic_client.rs
+++ b/core/sdk/src/quic/quic_client.rs
@@ -34,7 +34,7 @@ use iggy_common::{
use quinn::crypto::rustls::QuicClientConfig as QuinnQuicClientConfig;
use quinn::{ClientConfig, Connection, Endpoint, IdleTimeout, RecvStream,
VarInt};
use rustls::crypto::CryptoProvider;
-use std::net::SocketAddr;
+use std::net::{SocketAddr, ToSocketAddrs};
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
@@ -165,14 +165,13 @@ impl QuicClient {
/// Create a new QUIC client for the provided configuration.
pub fn create(config: Arc<QuicClientConfig>) -> Result<Self, IggyError> {
- let server_address = config
+ let resolved_addr = config
.server_address
- .parse::<SocketAddr>()
- .map_err(|error| {
- error!("Invalid server address: {error}");
- IggyError::InvalidServerAddress
- })?;
- let client_address = if server_address.is_ipv6()
+ .to_socket_addrs()
+ .ok()
+ .and_then(|mut addrs| addrs.next());
+
+ let client_address = if resolved_addr.is_some_and(|a| a.is_ipv6())
&& config.client_address ==
QuicClientConfig::default().client_address
{
"[::1]:0"
@@ -195,6 +194,7 @@ impl QuicClient {
let mut endpoint = endpoint.unwrap();
endpoint.set_default_client_config(quic_config);
+ let server_address = config.server_address.clone();
Ok(Self {
config,
endpoint,
@@ -203,7 +203,7 @@ impl QuicClient {
events: broadcast(1000),
connected_at: Mutex::new(None),
leader_redirection_state:
Mutex::new(LeaderRedirectionState::new()),
- current_server_address: Mutex::new(server_address.to_string()),
+ current_server_address: Mutex::new(server_address),
})
}
@@ -304,13 +304,20 @@ impl QuicClient {
let remote_address;
loop {
let server_address_str =
self.current_server_address.lock().await.clone();
- let server_address: SocketAddr =
server_address_str.parse().map_err(|e| {
- error!(
- "Failed to parse server address '{}': {}",
- server_address_str, e
- );
- IggyError::InvalidServerAddress
- })?;
+ let server_address =
tokio::net::lookup_host(&server_address_str)
+ .await
+ .map_err(|e| {
+ error!(
+ "Failed to resolve server address '{}': {}",
+ server_address_str, e
+ );
+ IggyError::InvalidServerAddress
+ })?
+ .next()
+ .ok_or_else(|| {
+ error!("No addresses resolved for '{}'",
server_address_str);
+ IggyError::InvalidServerAddress
+ })?;
info!(
"{NAME} client is connecting to server: {}...",
server_address
@@ -912,4 +919,46 @@ mod tests {
IggyDuration::from_str("5s").unwrap()
);
}
+
+ #[tokio::test]
+ async fn should_create_with_hostname_address() {
+ let config = QuicClientConfig {
+ server_address: "localhost:8080".to_string(),
+ ..Default::default()
+ };
+ let client = QuicClient::create(Arc::new(config));
+ assert!(client.is_ok(), "Expected Ok, got: {:?}", client.err());
+ }
+
+ #[tokio::test]
+ async fn should_create_with_fqdn_address() {
+ let config = QuicClientConfig {
+ server_address: "my-server.example.com:8080".to_string(),
+ ..Default::default()
+ };
+ let client = QuicClient::create(Arc::new(config));
+ assert!(client.is_ok(), "Expected Ok, got: {:?}", client.err());
+ }
+
+ #[tokio::test]
+ async fn should_store_raw_hostname_in_current_server_address() {
+ let hostname = "localhost:8080";
+ let config = QuicClientConfig {
+ server_address: hostname.to_string(),
+ ..Default::default()
+ };
+ let client = QuicClient::create(Arc::new(config)).unwrap();
+ let stored = client.current_server_address.lock().await;
+ assert_eq!(*stored, hostname);
+ }
+
+ #[tokio::test]
+ async fn should_succeed_from_connection_string_with_hostname() {
+ let connection_string = "iggy+quic://user:secret@localhost:1234";
+ let client = QuicClient::from_connection_string(connection_string);
+ assert!(client.is_ok());
+
+ let client = client.unwrap();
+ assert_eq!(client.config.server_address, "localhost:1234");
+ }
}
diff --git a/core/sdk/src/websocket/websocket_client.rs
b/core/sdk/src/websocket/websocket_client.rs
index ff89d2a30..c64233daf 100644
--- a/core/sdk/src/websocket/websocket_client.rs
+++ b/core/sdk/src/websocket/websocket_client.rs
@@ -217,10 +217,20 @@ impl WebSocketClient {
}
}
- let server_addr =
current_address.parse::<SocketAddr>().map_err(|_| {
- error!("Invalid server address: {}", current_address);
- IggyError::InvalidConfiguration
- })?;
+ let server_addr = tokio::net::lookup_host(&*current_address)
+ .await
+ .map_err(|e| {
+ error!(
+ "Failed to resolve server address '{}': {}",
+ current_address, e
+ );
+ IggyError::InvalidConfiguration
+ })?
+ .next()
+ .ok_or_else(|| {
+ error!("No addresses resolved for '{}'",
current_address);
+ IggyError::InvalidConfiguration
+ })?;
let connection_stream = if self.config.tls_enabled {
match self.connect_tls(server_addr, &mut
retry_count).await {
@@ -715,4 +725,14 @@ mod tests {
let client =
WebSocketClient::from_connection_string(connection_string);
assert!(client.is_err());
}
+
+ #[test]
+ fn should_succeed_from_connection_string_with_hostname() {
+ let connection_string = "iggy+ws://user:secret@localhost:8092";
+ let client =
WebSocketClient::from_connection_string(connection_string);
+ assert!(client.is_ok());
+
+ let client = client.unwrap();
+ assert_eq!(client.config.server_address, "localhost:8092");
+ }
}