use std::future::Future;
use std::{env, io};
use tokio::net::TcpListener;
use crate::router::{RouterService, internal_serve};
pub async fn serve(
listener: TcpListener,
service: impl Into<RouterService>,
) -> Result<(), io::Error> {
serve_until(listener, service, shutdown_signal()).await
}
pub async fn serve_until(
listener: TcpListener,
service: impl Into<RouterService>,
signal: impl Future<Output = ()>,
) -> Result<(), io::Error> {
let addr = listener.local_addr().ok();
crate::dev::notify_ready(addr).await;
internal_serve(listener, service.into(), signal).await
}
pub async fn start(service: impl Into<RouterService>) -> Result<(), io::Error> {
let host = host_from_env()?;
let port = port_from_env()?;
let listener = TcpListener::bind((host.as_str(), port)).await?;
serve(listener, service).await
}
async fn shutdown_signal() {
let ctrl_c = async {
tokio::signal::ctrl_c()
.await
.expect("failed to install the Ctrl+C signal handler");
};
#[cfg(unix)]
let terminate = async {
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("failed to install the SIGTERM signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
() = ctrl_c => {}
() = terminate => {}
}
}
fn host_from_env() -> Result<String, io::Error> {
const HOST_ENV: &str = "HOST";
const DEFAULT_HOST: &str = "127.0.0.1";
match env::var(HOST_ENV) {
Ok(value) => Ok(value),
Err(env::VarError::NotPresent) => Ok(DEFAULT_HOST.to_owned()),
Err(error) => Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("{HOST_ENV} must be valid Unicode: {error}"),
)),
}
}
fn port_from_env() -> Result<u16, io::Error> {
const PORT_ENV: &str = "PORT";
const DEFAULT_PORT: u16 = 3000;
match env::var(PORT_ENV) {
Ok(value) => value.parse().map_err(|error| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("{PORT_ENV} must be a valid port number: {error}"),
)
}),
Err(env::VarError::NotPresent) => Ok(DEFAULT_PORT),
Err(error) => Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("{PORT_ENV} must be valid Unicode: {error}"),
)),
}
}