use std::sync::Arc;
use hyper_util::rt::{TokioExecutor, TokioIo};
pub async fn serve_router(
listener: tokio::net::TcpListener,
router: axum::Router,
https: Option<&oj_config::HttpsConfig>,
) -> anyhow::Result<()> {
let Some(https) = https else {
axum::serve(listener, router).await?;
return Ok(());
};
let acceptor = tokio_rustls::TlsAcceptor::from(tls_config(https)?);
loop {
let (tcp, _remote) = match listener.accept().await {
Ok(conn) => conn,
Err(e) => {
use std::io::ErrorKind;
if !matches!(
e.kind(),
ErrorKind::ConnectionRefused
| ErrorKind::ConnectionAborted
| ErrorKind::ConnectionReset
) {
eprintln!("accept error: {e}");
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
continue;
}
};
let acceptor = acceptor.clone();
let router = router.clone();
tokio::spawn(async move {
let Ok(stream) = acceptor.accept(tcp).await else {
return;
};
let service = hyper_util::service::TowerToHyperService::new(router);
let _ = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new())
.serve_connection_with_upgrades(TokioIo::new(stream), service)
.await;
});
}
}
pub fn url_scheme(https: Option<&oj_config::HttpsConfig>) -> &'static str {
if https.is_some() {
"https"
} else {
"http"
}
}
fn tls_config(https: &oj_config::HttpsConfig) -> anyhow::Result<Arc<rustls::ServerConfig>> {
let certs: Vec<_> = rustls_pemfile::certs(&mut https.cert.as_bytes())
.collect::<Result<_, _>>()
.map_err(|e| anyhow::anyhow!("server.https.cert is not readable PEM: {e}"))?;
if certs.is_empty() {
anyhow::bail!("server.https.cert holds no certificate");
}
let key = rustls_pemfile::private_key(&mut https.key.as_bytes())
.map_err(|e| anyhow::anyhow!("server.https.key is not readable PEM: {e}"))?
.ok_or_else(|| anyhow::anyhow!("server.https.key holds no private key"))?;
let mut cfg = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, key)
.map_err(|e| anyhow::anyhow!("server.https key/cert rejected: {e}"))?;
cfg.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
Ok(Arc::new(cfg))
}