1use axum::Router;
2use std::net::SocketAddr;
3use std::time::Duration;
4use tower_http::cors::CorsLayer;
5use tower_http::limit::RequestBodyLimitLayer;
6use tower_http::timeout::TimeoutLayer;
7use tower_http::trace::TraceLayer;
8
9fn print_listening_banner(scheme: &str, addr: SocketAddr) {
15 let url = format!("{scheme}://{addr}");
16 println!();
17 println!(" \x1b[1;36m⚡ ling-http\x1b[0m");
18 println!(" \x1b[32m➜\x1b[0m Running on \x1b[1;32m{url}\x1b[0m");
19 println!(" \x1b[32m➜\x1b[0m Press \x1b[1mCtrl+C\x1b[0m to quit");
20 println!();
21}
22
23#[cfg(feature = "dev-certs")]
24use crate::tls::TlsMaterial;
25
26pub struct App<S> {
33 router: Router<S>,
34}
35
36impl<S> App<S>
37where
38 S: Clone + Send + Sync + 'static,
39{
40 pub fn new() -> Self {
45 let router = Router::new()
46 .layer(TraceLayer::new_for_http())
47 .layer(TimeoutLayer::with_status_code(
48 axum::http::StatusCode::REQUEST_TIMEOUT,
49 Duration::from_secs(60),
50 ))
51 .layer(RequestBodyLimitLayer::new(25 * 1024 * 1024))
52 .layer(CorsLayer::permissive());
53 Self { router }
54 }
55
56 pub fn max_body_bytes(mut self, bytes: usize) -> Self {
59 self.router = self.router.layer(RequestBodyLimitLayer::new(bytes));
60 self
61 }
62
63 pub fn merge(mut self, routes: Router<S>) -> Self {
66 self.router = self.router.merge(routes);
67 self
68 }
69
70 pub fn with_state(self, state: S) -> Router {
72 self.router.with_state(state)
73 }
74}
75
76impl<S> Default for App<S>
77where
78 S: Clone + Send + Sync + 'static,
79{
80 fn default() -> Self {
81 Self::new()
82 }
83}
84
85pub async fn serve_http(router: Router, addr: SocketAddr) -> anyhow::Result<()> {
90 let listener = tokio::net::TcpListener::bind(addr).await?;
91 let bound_addr = listener.local_addr().unwrap_or(addr);
92 tracing::info!(%bound_addr, "ling-http listening (plain HTTP)");
93 print_listening_banner("http", bound_addr);
94 axum::serve(listener, router.into_make_service()).await?;
95 Ok(())
96}
97
98pub async fn serve_tls(
102 router: Router,
103 addr: SocketAddr,
104 cert_pem: impl AsRef<std::path::Path>,
105 key_pem: impl AsRef<std::path::Path>,
106) -> anyhow::Result<()> {
107 let config = crate::tls::load_rustls_config(cert_pem, key_pem).await?;
108 tracing::info!(%addr, "ling-http listening (HTTPS)");
109 print_listening_banner("https", addr);
110 axum_server::bind_rustls(addr, config)
111 .serve(router.into_make_service())
112 .await?;
113 Ok(())
114}
115
116#[cfg(feature = "dev-certs")]
121pub async fn serve_dev_tls(router: Router, addr: SocketAddr) -> anyhow::Result<()> {
122 let TlsMaterial { config, .. } = crate::tls::generate_dev_cert(addr).await?;
123 tracing::warn!(%addr, "ling-http listening (HTTPS, SELF-SIGNED DEV CERT — do not use in production)");
124 print_listening_banner("https", addr);
125 println!(" \x1b[33m⚠\x1b[0m self-signed dev cert — browsers/curl will reject it as untrusted\n");
126 axum_server::bind_rustls(addr, config)
127 .serve(router.into_make_service())
128 .await?;
129 Ok(())
130}