use super::StreamProtocol;
use crate::common::EchoClient;
use crate::network::Address;
use crate::{EchoError, Result};
use async_trait::async_trait;
use bytes::BytesMut;
use std::time::Duration;
use tokio::time::{Instant, timeout};
#[derive(Debug, Clone)]
pub struct ClientConfig {
pub read_timeout: Duration,
pub write_timeout: Duration,
pub connect_timeout: Duration,
pub buffer_size: usize,
pub max_response_size: usize,
}
impl Default for ClientConfig {
fn default() -> Self {
Self {
read_timeout: Duration::from_secs(30),
write_timeout: Duration::from_secs(30),
connect_timeout: Duration::from_secs(10),
buffer_size: 1024,
max_response_size: 10 * 1024 * 1024, }
}
}
pub struct Client<P: StreamProtocol> {
stream: P::Stream,
config: ClientConfig,
last_activity: Instant,
}
impl<P: StreamProtocol> Client<P>
where
P::Error: Into<EchoError> + std::fmt::Display,
{
pub async fn connect_with_config<A: Into<Address>>(
address: A,
config: ClientConfig,
) -> Result<Self> {
let address = address.into();
let stream = match &address {
Address::Network(addr) => timeout(config.connect_timeout, P::connect(*addr))
.await
.map_err(|_| EchoError::Timeout("Connection timeout".to_string()))?
.map_err(|e| e.into())?,
Address::Unix(_) => {
return Err(EchoError::Unsupported(
"Use Unix-specific client for Unix domain sockets".to_string(),
));
}
};
Ok(Self {
stream,
config,
last_activity: Instant::now(),
})
}
pub async fn connect<A: Into<Address>>(address: A) -> Result<Self> {
Self::connect_with_config(address, ClientConfig::default()).await
}
pub fn is_idle(&self, max_idle: Duration) -> bool {
self.last_activity.elapsed() > max_idle
}
fn update_activity(&mut self) {
self.last_activity = Instant::now();
}
async fn send_and_receive(&mut self, data: &[u8]) -> Result<Vec<u8>> {
self.update_activity();
timeout(self.config.write_timeout, P::write(&mut self.stream, data))
.await
.map_err(|_| EchoError::Timeout("Write timeout".to_string()))?
.map_err(|e| e.into())?;
timeout(self.config.write_timeout, P::flush(&mut self.stream))
.await
.map_err(|_| EchoError::Timeout("Flush timeout".to_string()))?
.map_err(|e| e.into())?;
let mut response = BytesMut::with_capacity(self.config.buffer_size);
let mut buffer = vec![0u8; self.config.buffer_size];
loop {
let read_result = timeout(
self.config.read_timeout,
P::read(&mut self.stream, &mut buffer),
)
.await;
match read_result {
Ok(Ok(0)) => {
break;
}
Ok(Ok(n)) => {
if response.len() + n > self.config.max_response_size {
return Err(EchoError::Config(format!(
"Response too large: {} bytes, max allowed: {}",
response.len() + n,
self.config.max_response_size
)));
}
response.extend_from_slice(&buffer[..n]);
if response.len() >= data.len() {
break;
}
}
Ok(Err(e)) => {
return Err(e.into());
}
Err(_) => {
if response.len() >= data.len() {
break;
} else {
return Err(EchoError::Timeout(format!(
"Read timeout: expected {} bytes, got {} bytes",
data.len(),
response.len()
)));
}
}
}
}
self.update_activity();
Ok(response.to_vec())
}
pub fn config(&self) -> &ClientConfig {
&self.config
}
pub fn set_config(&mut self, config: ClientConfig) {
self.config = config;
}
}
#[async_trait]
impl<P: StreamProtocol> EchoClient for Client<P>
where
P::Error: Into<EchoError> + std::fmt::Display,
{
async fn echo(&mut self, data: &[u8]) -> Result<Vec<u8>> {
if data.is_empty() {
return Ok(Vec::new());
}
if data.len() > self.config.max_response_size {
return Err(EchoError::Config(format!(
"Request too large: {} bytes, max allowed: {}",
data.len(),
self.config.max_response_size
)));
}
self.send_and_receive(data).await
}
}
pub struct ClientConfigBuilder {
config: ClientConfig,
}
impl ClientConfigBuilder {
pub fn new() -> Self {
Self {
config: ClientConfig::default(),
}
}
pub fn read_timeout(mut self, timeout: Duration) -> Self {
self.config.read_timeout = timeout;
self
}
pub fn write_timeout(mut self, timeout: Duration) -> Self {
self.config.write_timeout = timeout;
self
}
pub fn connect_timeout(mut self, timeout: Duration) -> Self {
self.config.connect_timeout = timeout;
self
}
pub fn buffer_size(mut self, size: usize) -> Self {
self.config.buffer_size = size;
self
}
pub fn max_response_size(mut self, size: usize) -> Self {
self.config.max_response_size = size;
self
}
pub fn build(self) -> ClientConfig {
self.config
}
}
impl Default for ClientConfigBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_client_config_builder() {
let config = ClientConfigBuilder::new()
.read_timeout(Duration::from_secs(60))
.write_timeout(Duration::from_secs(30))
.buffer_size(2048)
.max_response_size(1024 * 1024)
.build();
assert_eq!(config.read_timeout, Duration::from_secs(60));
assert_eq!(config.write_timeout, Duration::from_secs(30));
assert_eq!(config.buffer_size, 2048);
assert_eq!(config.max_response_size, 1024 * 1024);
}
#[tokio::test]
async fn test_client_idle_detection() {
use std::net::SocketAddr;
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let _address = Address::Network(addr);
let config = ClientConfigBuilder::new()
.connect_timeout(Duration::from_millis(100))
.build();
assert_eq!(config.connect_timeout, Duration::from_millis(100));
}
#[test]
fn test_size_validation() {
let config = ClientConfig {
max_response_size: 100,
..Default::default()
};
assert!(config.max_response_size == 100);
}
}