use std::net::ToSocketAddrs;
use std::sync::Arc;
use crate::error::{Error, Result};
use crate::handler::Handler;
use crate::proto::{Request, Response, StatusCode};
use crate::session::SessionConfig;
use crate::static_files::StaticFiles;
#[cfg(feature = "compress")]
use crate::compress;
#[cfg(feature = "tls")]
use crate::tls::TlsAcceptor;
pub(crate) mod common;
pub(crate) mod redirect;
#[cfg(feature = "acme")]
pub(crate) mod route;
#[cfg(feature = "rt-mio")]
mod mio;
#[cfg(feature = "h3")]
mod quic;
#[cfg(feature = "rt-threadpool")]
mod threadpool;
#[cfg(feature = "rt-tokio")]
mod tokio;
#[cfg(feature = "acme")]
use crate::acme::AcmeManager;
#[cfg(feature = "rt-threadpool")]
pub(crate) enum TlsMode {
Plain,
#[cfg(feature = "tls")]
Static(TlsAcceptor),
#[cfg(feature = "acme")]
Acme(AcmeManager),
}
fn not_found(_req: &Request) -> Response {
Response::status(StatusCode::NOT_FOUND)
}
pub struct Server {
addrs: Vec<std::net::SocketAddr>,
handler: Arc<dyn Handler>,
server_name: Option<String>,
workers: usize,
#[cfg(feature = "tls")]
tls: Option<TlsAcceptor>,
#[cfg(feature = "compress")]
compression: compress::Options,
hsts: Option<String>,
alt_svc: Option<String>,
allow_http: bool,
http_addrs: Vec<std::net::SocketAddr>,
#[cfg(feature = "acme")]
acme: Option<AcmeManager>,
}
impl Server {
pub fn bind(addr: impl ToSocketAddrs) -> Result<Server> {
let addrs: Vec<_> = addr.to_socket_addrs()?.collect();
if addrs.is_empty() {
return Err(Error::Config("no socket address resolved".into()));
}
Ok(Server {
addrs,
handler: Arc::new(not_found),
server_name: Some(concat!("httpsd/", env!("CARGO_PKG_VERSION")).to_owned()),
workers: default_workers(),
#[cfg(feature = "tls")]
tls: None,
#[cfg(feature = "compress")]
compression: compress::Options::default(),
hsts: None,
alt_svc: None,
allow_http: false,
http_addrs: Vec::new(),
#[cfg(feature = "acme")]
acme: None,
})
}
pub fn handler<H: Handler + 'static>(mut self, handler: H) -> Server {
self.handler = Arc::new(handler);
self
}
pub fn handler_arc(mut self, handler: Arc<dyn Handler>) -> Server {
self.handler = handler;
self
}
pub fn serve_dir(self, root: impl Into<std::path::PathBuf>) -> Server {
self.handler(StaticFiles::new(root))
}
pub fn workers(mut self, workers: usize) -> Server {
self.workers = workers.max(1);
self
}
pub fn server_name(mut self, name: Option<String>) -> Server {
self.server_name = name;
self
}
#[cfg(feature = "tls")]
pub fn tls(mut self, acceptor: TlsAcceptor) -> Server {
self.tls = Some(acceptor);
self
}
#[cfg(feature = "compress")]
pub fn compression(mut self, options: compress::Options) -> Server {
self.compression = options;
self
}
pub fn hsts(mut self, value: Option<String>) -> Server {
self.hsts = value;
self
}
pub fn allow_http(mut self, allow: bool) -> Server {
self.allow_http = allow;
self
}
pub fn http_redirect(mut self, addr: impl ToSocketAddrs) -> Result<Server> {
self.http_addrs = addr.to_socket_addrs()?.collect();
Ok(self)
}
#[cfg(feature = "acme")]
pub fn acme(mut self, manager: AcmeManager) -> Server {
self.acme = Some(manager);
self
}
pub fn alt_svc(mut self, value: Option<String>) -> Server {
self.alt_svc = value;
self
}
fn session_config(&self) -> SessionConfig {
SessionConfig {
handler: Arc::clone(&self.handler),
limits: crate::proto::Limits::default(),
server_name: self.server_name.clone(),
hsts: self.hsts.clone(),
alt_svc: self.alt_svc.clone(),
#[cfg(feature = "compress")]
compression: self.compression,
}
}
fn http_ctx(&self) -> redirect::HttpCtx {
redirect::HttpCtx {
allow_http: self.allow_http,
server_name: self.server_name.clone(),
limits: crate::proto::Limits::default(),
content: self.allow_http.then(|| Arc::clone(&self.handler)),
#[cfg(feature = "acme")]
acme: self.acme.clone(),
#[cfg(feature = "compress")]
compression: self.compression,
}
}
#[cfg(feature = "rt-threadpool")]
fn tls_mode(&self) -> TlsMode {
#[cfg(feature = "acme")]
if let Some(mgr) = &self.acme {
return TlsMode::Acme(mgr.clone());
}
#[cfg(feature = "tls")]
if let Some(acc) = &self.tls {
return TlsMode::Static(acc.clone());
}
TlsMode::Plain
}
#[cfg(feature = "rt-threadpool")]
pub fn run(self) -> Result<()> {
let listener = std::net::TcpListener::bind(self.addrs.as_slice())?;
let cfg = self.session_config();
let tls_mode = self.tls_mode();
if !self.http_addrs.is_empty() {
let http = std::net::TcpListener::bind(self.http_addrs.as_slice())?;
let ctx = self.http_ctx();
std::thread::spawn(move || threadpool::run_http_redirect(http, ctx));
}
threadpool::run(listener, cfg, tls_mode, self.workers)
}
#[cfg(feature = "rt-tokio")]
pub async fn run_tokio(self) -> Result<()> {
let cfg = self.session_config();
tokio::run(
self.addrs.clone(),
cfg,
#[cfg(feature = "tls")]
self.tls,
)
.await
}
#[cfg(feature = "rt-mio")]
pub fn run_mio(self) -> Result<()> {
let cfg = self.session_config();
mio::run(
self.addrs.clone(),
cfg,
#[cfg(feature = "tls")]
self.tls,
)
}
#[cfg(feature = "h3")]
pub fn run_h3(self) -> Result<()> {
let certs = self.h3_cert_source()?;
let cfg = self.session_config();
quic::run(self.addrs.clone(), cfg, certs)
}
#[cfg(feature = "h3")]
fn h3_cert_source(&self) -> Result<quic::CertSource> {
#[cfg(feature = "acme")]
if let Some(mgr) = &self.acme {
return Ok(quic::CertSource::Acme(mgr.clone()));
}
#[cfg(feature = "tls")]
if let Some(acc) = &self.tls {
return Ok(quic::CertSource::Static(acc.clone()));
}
Err(Error::Config(
"HTTP/3 requires TLS: a static cert via .tls(), or ACME via .acme()".into(),
))
}
}
fn default_workers() -> usize {
let cores = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1);
cores.max(8)
}