| Crates.io | echosrv |
| lib.rs | echosrv |
| version | 0.3.0 |
| created_at | 2025-07-26 02:25:41.566092+00 |
| updated_at | 2025-07-27 04:26:00.095659+00 |
| description | A high-performance set of async echo server library built with Tokio for testing and development environments |
| homepage | https://github.com/divoxx/echosrv |
| repository | https://github.com/divoxx/echosrv |
| max_upload_size | |
| id | 1768635 |
| size | 294,969 |
A high-performance async echo server library built with Tokio, supporting TCP, UDP, HTTP, and Unix domain socket protocols. Perfect for development, testing, and when you need a network service with predictable, verifiable behavior.
# Run TCP server on default port 8080
cargo run tcp
# Run UDP server on default port 8080
cargo run udp
# Run TCP server on specific port
cargo run tcp 9000
# Run UDP server on specific port
cargo run udp 9090
# Run Unix domain stream server
cargo run unix-stream /tmp/echo.sock
# Run Unix domain datagram server
cargo run unix-dgram /tmp/echo_dgram.sock
# Run HTTP server on default port 8080
cargo run http
# Run HTTP server on specific port
cargo run http 9000
# Test TCP with netcat
echo "Hello!" | nc localhost 8080
# Test UDP with netcat
echo "Hello!" | nc -u localhost 8080
# Test Unix domain socket with socat
echo "Hello!" | socat - UNIX-CONNECT:/tmp/echo.sock
# Test HTTP with curl
curl -X POST -d "Hello, HTTP!" http://localhost:8080/
use echosrv::tcp::{TcpConfig, TcpEchoServer};
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = TcpConfig {
bind_addr: "127.0.0.1:8080".parse()?,
max_connections: 100,
buffer_size: 1024,
read_timeout: Duration::from_secs(30),
write_timeout: Duration::from_secs(30),
};
let server = TcpEchoServer::new(config);
server.run().await?;
Ok(())
}
use echosrv::udp::{UdpConfig, UdpEchoServer};
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = UdpConfig {
bind_addr: "127.0.0.1:8080".parse()?,
buffer_size: 1024,
read_timeout: Duration::from_secs(30),
write_timeout: Duration::from_secs(30),
};
let server = UdpEchoServer::new(config);
server.run().await?;
Ok(())
}
use echosrv::http::{HttpConfig, HttpEchoServer};
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = HttpConfig {
bind_addr: "127.0.0.1:8080".parse()?,
max_connections: 100,
buffer_size: 8192,
read_timeout: Duration::from_secs(30),
write_timeout: Duration::from_secs(30),
server_name: Some("EchoServer/1.0".to_string()),
echo_headers: true,
default_content_type: Some("text/plain".to_string()),
};
let server = HttpEchoServer::new(config.into());
server.run().await?;
Ok(())
}
Note: The HTTP echo server only accepts POST requests and echoes back only the request body content (no HTTP headers). Non-POST requests receive a 405 Method Not Allowed response.
use echosrv::unix::{UnixStreamConfig, UnixStreamEchoServer};
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = UnixStreamConfig {
socket_path: "/tmp/echo.sock".into(),
max_connections: 100,
buffer_size: 1024,
read_timeout: Duration::from_secs(30),
write_timeout: Duration::from_secs(30),
};
let server = UnixStreamEchoServer::new(config);
server.run().await?;
Ok(())
}
use echosrv::unix::{UnixDatagramConfig, UnixDatagramEchoServer};
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = UnixDatagramConfig {
socket_path: "/tmp/echo_dgram.sock".into(),
buffer_size: 1024,
read_timeout: Duration::from_secs(30),
write_timeout: Duration::from_secs(30),
};
let server = UnixDatagramEchoServer::new(config);
server.run().await?;
Ok(())
}
let config = TcpConfig {
bind_addr: "127.0.0.1:8080".parse().unwrap(),
max_connections: 1000, // Max concurrent connections
buffer_size: 1024, // Read/write buffer size
read_timeout: Duration::from_secs(30), // Read timeout
write_timeout: Duration::from_secs(30), // Write timeout
};
let config = UdpConfig {
bind_addr: "127.0.0.1:8080".parse().unwrap(),
buffer_size: 1024, // Read/write buffer size
read_timeout: Duration::from_secs(30), // Read timeout
write_timeout: Duration::from_secs(30), // Write timeout
};
let config = UnixStreamConfig {
socket_path: "/tmp/echo.sock".into(), // Unix socket file path
max_connections: 100, // Max concurrent connections
buffer_size: 1024, // Read/write buffer size
read_timeout: Duration::from_secs(30), // Read timeout
write_timeout: Duration::from_secs(30), // Write timeout
};
let config = UnixDatagramConfig {
socket_path: "/tmp/echo_dgram.sock".into(), // Unix socket file path
buffer_size: 1024, // Read/write buffer size
read_timeout: Duration::from_secs(30), // Read timeout
write_timeout: Duration::from_secs(30), // Write timeout
};
The library includes test clients for both protocols:
use echosrv::tcp::TcpEchoClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "127.0.0.1:8080".parse()?;
let mut client = TcpEchoClient::connect(addr).await?;
let response = client.echo_string("Hello, TCP Server!").await?;
println!("Server echoed: {}", response);
Ok(())
}
use echosrv::udp::UdpEchoClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "127.0.0.1:8080".parse()?;
let mut client = UdpEchoClient::connect(addr).await?;
let response = client.echo_string("Hello, UDP Server!").await?;
println!("Server echoed: {}", response);
Ok(())
}
use echosrv::unix::UnixStreamEchoClient;
use std::path::PathBuf;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let socket_path = PathBuf::from("/tmp/echo.sock");
let mut client = UnixStreamEchoClient::connect(socket_path).await?;
let response = client.echo_string("Hello, Unix Stream Server!").await?;
println!("Server echoed: {}", response);
Ok(())
}
use echosrv::http::HttpEchoClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "127.0.0.1:8080".parse()?;
let mut client = HttpEchoClient::connect(addr).await?;
let response = client.echo_string("Hello, HTTP Server!").await?;
println!("Server echoed: {}", response);
Ok(())
}
Note: The HTTP client sends POST requests and receives only the body content in response.
use echosrv::unix::UnixDatagramEchoClient;
use std::path::PathBuf;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let socket_path = PathBuf::from("/tmp/echo_dgram.sock");
let mut client = UnixDatagramEchoClient::connect(socket_path).await?;
let response = client.echo_string("Hello, Unix Datagram Server!").await?;
println!("Server echoed: {}", response);
Ok(())
}
For extensibility, you can also use the generic client implementations:
use echosrv::stream::StreamEchoClient;
use echosrv::datagram::DatagramEchoClient;
use echosrv::tcp::TcpProtocol;
use echosrv::udp::UdpProtocol;
// Generic stream client with TCP protocol
let mut tcp_client: StreamEchoClient<TcpProtocol> = StreamEchoClient::connect(addr).await?;
// Generic datagram client with UDP protocol
let mut udp_client: DatagramEchoClient<UdpProtocol> = DatagramEchoClient::connect(addr).await?;
// Both work identically to the concrete clients
let response = client.echo_string("Hello!").await?;
The library uses a clean, generic architecture for maximum extensibility:
src/
├── common/ # Shared components
│ ├── traits.rs # Core traits (EchoServerTrait, EchoClient)
│ └── test_utils.rs # Test utilities
├── stream/ # Generic stream implementation
│ ├── client.rs # Generic stream client
│ ├── server.rs # Generic stream server
│ ├── protocol.rs # StreamProtocol trait
│ └── config.rs # StreamConfig
├── datagram/ # Generic datagram implementation
│ ├── client.rs # Generic datagram client
│ ├── server.rs # Generic datagram server
│ ├── protocol.rs # DatagramProtocol trait
│ └── config.rs # DatagramConfig
├── tcp/ # TCP-specific implementation
│ ├── mod.rs # Type aliases for TCP
│ ├── server.rs # Type alias: TcpEchoServer = StreamEchoServer<TcpProtocol>
│ ├── config.rs # TcpConfig
│ └── stream_protocol.rs # TcpProtocol implementation
├── udp/ # UDP-specific implementation
│ ├── mod.rs # Type aliases for UDP
│ ├── server.rs # Type alias: UdpEchoServer = DatagramEchoServer<UdpProtocol>
│ ├── config.rs # UdpConfig
│ └── datagram_protocol.rs # UdpProtocol implementation
├── unix/ # Unix domain socket implementation
│ ├── mod.rs # Module exports and type aliases
│ ├── config.rs # UnixStreamConfig, UnixDatagramConfig
│ ├── server.rs # UnixStreamEchoServer, UnixDatagramEchoServer
│ ├── client.rs # UnixStreamEchoClient, UnixDatagramEchoClient
│ ├── stream_protocol.rs # UnixStreamProtocol implementation
│ ├── datagram_protocol.rs # UnixDatagramProtocol implementation
│ └── tests.rs # Unix domain socket tests
├── http/ # HTTP protocol implementation
│ ├── mod.rs # Module exports and type aliases
│ ├── config.rs # HttpConfig
│ ├── protocol.rs # HttpProtocol implementation
│ ├── client.rs # HttpEchoClient type alias
│ └── tests.rs # HTTP protocol unit tests
├── lib.rs # Main library exports
└── main.rs # Binary entry point
TcpEchoClient, UdpEchoClient) are type aliases to generic implementationsStreamProtocol and DatagramProtocol traits define the interface for protocol implementationsBoth TCP and UDP implementations share common traits for consistency:
use echosrv::common::{EchoServerTrait, EchoClient};
// Both TcpEchoServer and UdpEchoServer implement EchoServerTrait
// Both TcpEchoClient and UdpEchoClient implement EchoClient
Add to your Cargo.toml:
[dependencies]
echosrv = "0.1.0"
MIT License - see LICENSE file for details.