use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use axum::Router;
use thiserror::Error;
use tokio::net::TcpListener;
use tokio_rustls::TlsAcceptor;
use tower::ServiceExt;
#[derive(Debug, Error)]
pub enum TlsError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("No valid certificate found")]
NoCertificate,
#[error("No valid private key found")]
NoPrivateKey,
#[error("TLS error: {0}")]
Tls(String),
}
#[derive(Debug, Clone)]
pub struct TlsConfig {
pub cert_path: PathBuf,
pub key_path: PathBuf,
pub alpn: Vec<Vec<u8>>,
}
impl TlsConfig {
pub fn new(cert_path: impl Into<PathBuf>, key_path: impl Into<PathBuf>) -> Self {
Self {
cert_path: cert_path.into(),
key_path: key_path.into(),
alpn: vec![b"h2".to_vec(), b"http/1.1".to_vec()],
}
}
pub fn h2_only(mut self) -> Self {
self.alpn = vec![b"h2".to_vec()];
self
}
pub fn http1_only(mut self) -> Self {
self.alpn = vec![b"http/1.1".to_vec()];
self
}
pub async fn build_acceptor(&self) -> Result<TlsAcceptor, TlsError> {
let cert = load_certs(&self.cert_path).await?;
let key = load_private_key(&self.key_path).await?;
let mut config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(cert, key)
.map_err(|e| TlsError::Tls(e.to_string()))?;
config.alpn_protocols = self.alpn.clone();
Ok(TlsAcceptor::from(Arc::new(config)))
}
}
async fn load_certs(
path: &std::path::Path,
) -> Result<Vec<rustls::pki_types::CertificateDer<'static>>, TlsError> {
let file = tokio::fs::read_to_string(path).await?;
let mut reader = std::io::BufReader::new(file.as_bytes());
let certs: Vec<_> = rustls_pemfile::certs(&mut reader).collect::<Result<_, _>>()?;
if certs.is_empty() {
return Err(TlsError::NoCertificate);
}
Ok(certs)
}
async fn load_private_key(
path: &std::path::Path,
) -> Result<rustls::pki_types::PrivateKeyDer<'static>, TlsError> {
let file = tokio::fs::read_to_string(path).await?;
let mut reader = std::io::BufReader::new(file.as_bytes());
rustls_pemfile::private_key(&mut reader)
.map_err(TlsError::Io)?
.ok_or(TlsError::NoPrivateKey)
}
pub async fn serve_http2(router: Router, addr: SocketAddr, tls: TlsConfig) -> Result<(), TlsError> {
let acceptor = tls.build_acceptor().await?;
let listener = TcpListener::bind(addr).await?;
tracing::info!("HTTPS/HTTP2 server listening on {}", addr);
loop {
let (tcp_stream, _remote) = listener.accept().await?;
let acceptor = acceptor.clone();
let router = router.clone();
tokio::spawn(async move {
let tls_stream = match acceptor.accept(tcp_stream).await {
Ok(s) => s,
Err(e) => {
tracing::warn!("TLS handshake failed: {}", e);
return;
}
};
let io = hyper_util::rt::TokioIo::new(tls_stream);
let svc = hyper::service::service_fn(move |req| {
let router = router.clone();
async move { router.oneshot(req).await }
});
if let Err(e) =
hyper::server::conn::http2::Builder::new(hyper_util::rt::TokioExecutor::new())
.serve_connection(io, svc)
.await
{
tracing::warn!("HTTP/2 connection error: {}", e);
}
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tls_config_new() {
let config = TlsConfig::new("/path/to/cert.pem", "/path/to/key.pem");
assert_eq!(config.cert_path, PathBuf::from("/path/to/cert.pem"));
assert_eq!(config.key_path, PathBuf::from("/path/to/key.pem"));
assert_eq!(config.alpn, vec![b"h2".to_vec(), b"http/1.1".to_vec()]);
}
#[test]
fn test_tls_config_h2_only() {
let config = TlsConfig::new("/cert.pem", "/key.pem").h2_only();
assert_eq!(config.alpn, vec![b"h2".to_vec()]);
}
#[test]
fn test_tls_config_http1_only() {
let config = TlsConfig::new("/cert.pem", "/key.pem").http1_only();
assert_eq!(config.alpn, vec![b"http/1.1".to_vec()]);
}
#[test]
fn test_tls_error_display() {
let err = TlsError::NoCertificate;
assert_eq!(err.to_string(), "No valid certificate found");
let err = TlsError::NoPrivateKey;
assert_eq!(err.to_string(), "No valid private key found");
}
}