use axum::Router;
use std::net::SocketAddr;
use std::time::Duration;
use tower_http::cors::CorsLayer;
use tower_http::limit::RequestBodyLimitLayer;
use tower_http::timeout::TimeoutLayer;
use tower_http::trace::TraceLayer;
fn print_listening_banner(scheme: &str, addr: SocketAddr) {
let url = format!("{scheme}://{addr}");
println!();
println!(" \x1b[1;36m⚡ ling-http\x1b[0m");
println!(" \x1b[32m➜\x1b[0m Running on \x1b[1;32m{url}\x1b[0m");
println!(" \x1b[32m➜\x1b[0m Press \x1b[1mCtrl+C\x1b[0m to quit");
println!();
}
#[cfg(feature = "dev-certs")]
use crate::tls::TlsMaterial;
pub struct App<S> {
router: Router<S>,
}
impl<S> App<S>
where
S: Clone + Send + Sync + 'static,
{
pub fn new() -> Self {
let router = Router::new()
.layer(TraceLayer::new_for_http())
.layer(TimeoutLayer::with_status_code(
axum::http::StatusCode::REQUEST_TIMEOUT,
Duration::from_secs(60),
))
.layer(RequestBodyLimitLayer::new(25 * 1024 * 1024))
.layer(CorsLayer::permissive());
Self { router }
}
pub fn max_body_bytes(mut self, bytes: usize) -> Self {
self.router = self.router.layer(RequestBodyLimitLayer::new(bytes));
self
}
pub fn merge(mut self, routes: Router<S>) -> Self {
self.router = self.router.merge(routes);
self
}
pub fn with_state(self, state: S) -> Router {
self.router.with_state(state)
}
}
impl<S> Default for App<S>
where
S: Clone + Send + Sync + 'static,
{
fn default() -> Self {
Self::new()
}
}
pub async fn serve_http(router: Router, addr: SocketAddr) -> anyhow::Result<()> {
let listener = tokio::net::TcpListener::bind(addr).await?;
let bound_addr = listener.local_addr().unwrap_or(addr);
tracing::info!(%bound_addr, "ling-http listening (plain HTTP)");
print_listening_banner("http", bound_addr);
axum::serve(listener, router.into_make_service()).await?;
Ok(())
}
pub async fn serve_tls(
router: Router,
addr: SocketAddr,
cert_pem: impl AsRef<std::path::Path>,
key_pem: impl AsRef<std::path::Path>,
) -> anyhow::Result<()> {
let config = crate::tls::load_rustls_config(cert_pem, key_pem).await?;
tracing::info!(%addr, "ling-http listening (HTTPS)");
print_listening_banner("https", addr);
axum_server::bind_rustls(addr, config)
.serve(router.into_make_service())
.await?;
Ok(())
}
#[cfg(feature = "dev-certs")]
pub async fn serve_dev_tls(router: Router, addr: SocketAddr) -> anyhow::Result<()> {
let TlsMaterial { config, .. } = crate::tls::generate_dev_cert(addr).await?;
tracing::warn!(%addr, "ling-http listening (HTTPS, SELF-SIGNED DEV CERT — do not use in production)");
print_listening_banner("https", addr);
println!(" \x1b[33m⚠\x1b[0m self-signed dev cert — browsers/curl will reject it as untrusted\n");
axum_server::bind_rustls(addr, config)
.serve(router.into_make_service())
.await?;
Ok(())
}