Skip to content

Commit

Permalink
feat!: socket2 TCP socket configuration
Browse files Browse the repository at this point in the history
With default feature socket2 and the so called crate it is possible to create configurable TCP sockets. Needs MSRV 1.63.

Fixes issue tiny-http#143

There is a the new field socket_config: connection::SocketConfig in ServerConfig.
Call Server::new to create a server with your own config.

The defaults are...
keep_alive: true
no_delay: true
read_timeout: 10s
tcp_keepalive_interval: None
tcp_keepalive_time: 5s
write_timeout: 10s

README.md/Cargo.toml is going back to MSRV 1.60 and describes which feature needs a newer/higher Rust version.
  • Loading branch information
kolbma committed Jan 17, 2024
1 parent a4187cc commit d614bdd
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 7 deletions.
17 changes: 12 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,21 @@ repository = "https://github.com/tiny-http/tiny-http"
keywords = ["http", "server", "web"]
license = "MIT OR Apache-2.0"
edition = "2018"
rust-version = "1.61"
rust-version = "1.60"

# msrv:
# native-tls 1.60
# openssl 1.60
# rustls 1.61
# socket2 1.63

[package.metadata]
# minimum supported rust version 1.61 - dependencies rustls
msrv = "1.61"
# minimum supported rust version 1.60 - dependencies
msrv = "1.60"

[features]
default = ["log"]
default = ["log", "socket2"]
socket2 = ["dep:socket2"]
ssl = ["ssl-openssl"]
ssl-openssl = ["openssl", "zeroize"]
ssl-rustls = ["rustls", "rustls-pemfile", "zeroize"]
Expand All @@ -25,11 +32,11 @@ ssl-native-tls = ["native-tls", "zeroize"]
ascii = "1.1"
chunked_transfer = "1"
httpdate = "1.0.3"

log = { version = "0.4.20", optional = true }
openssl = { version = "0.10", optional = true }
rustls = { version = "0.22.2", optional = true }
rustls-pemfile = { version = "2.0.0", optional = true }
socket2 = { version = "0.5.5", optional = true }
zeroize = { version = "1", optional = true }
native-tls = { version = "0.2", optional = true }

Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,29 @@ Add this to the `Cargo.toml` file of your project:
tiny_http = "0.12"
```

#### Minimum Supported Rust Version

At least version __1.60__.
But feature __ssl-rustls__ needs __1.61__ and default feature __socket2__ needs __1.63__.


### Features

#### Default features

- log: uses log trait to debug and error
- socket2: provides configurable TCP socket

#### Optional features

- log: uses log trait to debug and error
- socket2: provides configurable TCP socket

Select single _ssl_ feature...
- ssl: HTTPS with openssl support
- ssl-native-tls: HTTPS with native-tls support
- ssl-rustls: HTTPS with rustls support

### Usage

```rust
Expand Down
116 changes: 114 additions & 2 deletions src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,26 +117,102 @@ impl From<unix_net::UnixStream> for Connection {

#[derive(Debug, Clone)]
pub enum ConfigListenAddr {
#[cfg(not(feature = "socket2"))]
IP(Vec<SocketAddr>),
#[cfg(feature = "socket2")]
IP((Vec<SocketAddr>, Option<SocketConfig>)),
#[cfg(unix)]
// TODO: use SocketAddr when bind_addr is stabilized
Unix(std::path::PathBuf),
}
impl ConfigListenAddr {
#[cfg(not(feature = "socket2"))]
pub fn from_socket_addrs<A: ToSocketAddrs>(addrs: A) -> std::io::Result<Self> {
addrs.to_socket_addrs().map(|it| Self::IP(it.collect()))
}

#[cfg(feature = "socket2")]
pub fn from_socket_addrs<A: ToSocketAddrs>(addrs: A) -> std::io::Result<Self> {
addrs
.to_socket_addrs()
.map(|it| Self::IP((it.collect(), None)))
}

#[cfg(feature = "socket2")]
pub fn with_config(mut self, config: SocketConfig) -> Self {
match &mut self {
ConfigListenAddr::IP((_, cfg)) => {
*cfg = Some(config);
self
}
ConfigListenAddr::Unix(_) => self,
}
}

#[cfg(unix)]
pub fn unix_from_path<P: Into<PathBuf>>(path: P) -> Self {
Self::Unix(path.into())
}

#[cfg(feature = "socket2")]
pub(crate) fn bind(&self, config: &SocketConfig) -> std::io::Result<Listener> {
match self {
Self::IP(ip) => {
let addresses = &ip.0;
let mut err = None;
let mut socket =
socket2::Socket::new(socket2::Domain::IPV4, socket2::Type::STREAM, None)?;

for address in addresses {
socket = socket2::Socket::new(
socket2::Domain::for_address(*address),
socket2::Type::STREAM,
None,
)?;

if let Err(e) = socket.bind(&(*address).into()) {
err = Some(e);
continue;
}
if let Err(e) = socket.listen(128) {
err = Some(e);
continue;
}
err = None;
break;
}

if let Some(err) = err {
return Err(err);
}

socket.set_keepalive(config.keep_alive)?;
socket.set_nodelay(config.no_delay)?;
socket.set_read_timeout(Some(config.read_timeout))?;
socket.set_tcp_keepalive(&if let Some(tcp_keepalive_interval) =
config.tcp_keepalive_interval
{
socket2::TcpKeepalive::new()
.with_interval(tcp_keepalive_interval)
.with_time(config.tcp_keepalive_time)
} else {
socket2::TcpKeepalive::new().with_time(config.tcp_keepalive_time)
})?;
socket.set_write_timeout(Some(config.write_timeout))?;

Ok(Listener::Tcp(socket.into()))
}
#[cfg(unix)]
Self::Unix(path) => unix_net::UnixListener::bind(path).map(Listener::from),
}
}

#[cfg(not(feature = "socket2"))]
pub(crate) fn bind(&self) -> std::io::Result<Listener> {
match self {
Self::IP(a) => TcpListener::bind(a.as_slice()).map(Listener::from),
Self::IP(addresses) => TcpListener::bind(addresses.as_slice()).map(Listener::from),
#[cfg(unix)]
Self::Unix(a) => unix_net::UnixListener::bind(a).map(Listener::from),
Self::Unix(path) => unix_net::UnixListener::bind(path).map(Listener::from),
}
}
}
Expand Down Expand Up @@ -192,3 +268,39 @@ impl std::fmt::Display for ListenAddr {
}
}
}

/// Config for TCP socket with enabled _socket2_ feature
///
/// # Defaults
///
/// keep_alive: true
/// no_delay: true
/// read_timeout: 10s
/// tcp_keepalive_interval: None
/// tcp_keepalive_time: 5s
/// write_timeout: 10s
///
#[cfg(feature = "socket2")]
#[derive(Clone, Debug)]
pub struct SocketConfig {
pub keep_alive: bool,
pub no_delay: bool,
pub read_timeout: std::time::Duration,
pub tcp_keepalive_interval: Option<std::time::Duration>,
pub tcp_keepalive_time: std::time::Duration,
pub write_timeout: std::time::Duration,
}

#[cfg(feature = "socket2")]
impl Default for SocketConfig {
fn default() -> Self {
Self {
keep_alive: true,
no_delay: true,
read_timeout: std::time::Duration::from_secs(10),
tcp_keepalive_interval: None,
tcp_keepalive_time: std::time::Duration::from_secs(5),
write_timeout: std::time::Duration::from_secs(10),
}
}
}
14 changes: 14 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ pub struct ServerConfig {
/// The addresses to try to listen to.
pub addr: ConfigListenAddr,

/// Socket configuration with _socket2_ feature
/// See [SocketConfig]
#[cfg(feature = "socket2")]
pub socket_config: connection::SocketConfig,

/// If `Some`, then the server will use SSL to encode the communications.
pub ssl: Option<SslConfig>,
}
Expand All @@ -202,6 +207,8 @@ impl Server {
{
Server::new(ServerConfig {
addr: ConfigListenAddr::from_socket_addrs(addr)?,
#[cfg(feature = "socket2")]
socket_config: connection::SocketConfig::default(),
ssl: None,
})
}
Expand All @@ -222,6 +229,8 @@ impl Server {
{
Server::new(ServerConfig {
addr: ConfigListenAddr::from_socket_addrs(addr)?,
#[cfg(feature = "socket2")]
socket_config: connection::SocketConfig::default(),
ssl: Some(config),
})
}
Expand All @@ -234,12 +243,17 @@ impl Server {
) -> Result<Server, Box<dyn Error + Send + Sync + 'static>> {
Server::new(ServerConfig {
addr: ConfigListenAddr::unix_from_path(path),
#[cfg(feature = "socket2")]
socket_config: connection::SocketConfig::default(),
ssl: None,
})
}

/// Builds a new server that listens on the specified address.
pub fn new(config: ServerConfig) -> Result<Server, Box<dyn Error + Send + Sync + 'static>> {
#[cfg(feature = "socket2")]
let listener = config.addr.bind(&config.socket_config)?;
#[cfg(not(feature = "socket2"))]
let listener = config.addr.bind()?;
Self::from_listener(listener, config.ssl)
}
Expand Down

0 comments on commit d614bdd

Please sign in to comment.