sweet_server/server/
mod.rs

1pub mod address;
2pub use self::address::*;
3pub mod tls;
4pub use self::tls::*;
5use anyhow::Result;
6use axum::Router;
7use axum::extract::Request;
8use axum::http::Method;
9use axum::response::Response;
10use axum::routing::get;
11use clap::Parser;
12use std::path::PathBuf;
13use std::thread::JoinHandle;
14use std::time::Duration;
15use sweet_fs::prelude::*;
16use sweet_utils::prelude::*;
17use tower_http::cors::CorsLayer;
18use tower_http::services::ServeDir;
19use tower_http::services::ServeFile;
20use tower_livereload::LiveReloadLayer;
21
22/// Serve static files with hot reloading
23#[derive(Debug, Clone, Parser)]
24#[command(name = "serve")]
25pub struct Server {
26	/// Directory to serve
27	#[arg(default_value = ".")]
28	pub dir: PathBuf,
29	/// Specify port
30	#[arg(long, default_value = "3000")]
31	pub port: String,
32	/// Specify host
33	#[arg(long, default_value = "0.0.0.0")]
34	pub host: String,
35	/// Run with https, *only for development*
36	#[arg(long)]
37	pub secure: bool,
38	// pub address: Address,
39	// do not clear the dir
40	#[arg(long)]
41	pub no_clear: bool,
42	#[arg(long)]
43	pub quiet: bool,
44	/// If a url is not found, fallback to the provided file
45	#[arg(long)]
46	pub fallback: Option<String>,
47	/// Add 'access-control-allow-origin: *' header
48	#[arg(long)]
49	pub any_origin: bool,
50	//
51	// [FsWatcher] args, we dont include because of dir/cwd collision
52	//
53	#[command(flatten)]
54	pub filter: GlobFilter,
55	/// debounce time in milliseconds
56	#[arg(short,long="debounce-millis",value_parser = parse_duration,default_value="50")]
57	pub debounce: Duration,
58}
59
60impl Default for Server {
61	fn default() -> Self { Self::parse_from(&[""]) }
62}
63
64impl Server {
65	pub fn with_dir(mut self, dir: impl Into<PathBuf>) -> Self {
66		self.dir = dir.into();
67		self
68	}
69	pub fn quietly(mut self) -> Self {
70		self.quiet = true;
71		self
72	}
73	pub async fn run(self) -> Result<()> {
74		self.serve_with_default_reload().await
75	}
76
77	pub async fn serve_with_default_reload(self) -> Result<()> {
78		let (livereload, _handle) = self.default_reload();
79		self.serve_with_reload(livereload).await
80	}
81
82	pub async fn serve_with_reload(
83		self,
84		livereload: LiveReloadLayer,
85	) -> Result<()> {
86		self.serve_with_options(Some(livereload)).await
87	}
88
89	pub async fn serve_with_options(
90		self,
91		livereload: Option<LiveReloadLayer>,
92	) -> Result<()> {
93		self.print_start();
94
95		let mut router = Router::new().route_service("/__ping__", get(ping));
96
97		if let Some(fallback) = &self.fallback {
98			router = router.fallback_service(
99				ServeDir::new(&self.dir)
100					.append_index_html_on_directories(true)
101					.fallback(ServeFile::new(fallback)),
102			);
103		} else {
104			router = router.fallback_service(
105				ServeDir::new(&self.dir).append_index_html_on_directories(true),
106			);
107		}
108		if let Some(livereload) = livereload {
109			router = router.layer(livereload);
110		}
111		if self.any_origin {
112			let cors = CorsLayer::new()
113				.allow_methods([Method::GET, Method::POST])
114				.allow_origin(tower_http::cors::Any);
115			router = router.layer(cors);
116		}
117
118		if self.secure {
119			self.serve_secure(router).await
120		} else {
121			self.serve_insecure(router).await
122		}
123	}
124
125	fn default_reload(&self) -> (LiveReloadLayer, JoinHandle<Result<()>>) {
126		let livereload = LiveReloadLayer::new();
127		let reload = livereload.reloader();
128		let this = self.clone();
129		let reload_handle = std::thread::spawn(move || -> Result<()> {
130			let this2 = this.clone();
131
132			FsWatcher {
133				infallible: false,
134				cwd: this.dir.clone(),
135				filter: this.filter.clone(),
136				debounce: this.debounce.clone(),
137			}
138			.watch_blocking(move |e| {
139				if let Some(events) = e.mutated_pretty() {
140					reload.reload();
141					println!("{}", events);
142					this2.print_start();
143				}
144				Ok(())
145			})
146		});
147		(livereload, reload_handle)
148	}
149
150	fn print_start(&self) {
151		if self.quiet {
152			return;
153		}
154		if self.no_clear == false {
155			// doesnt seem error worthy here
156			terminal::clear().ok();
157		}
158		let any_origin = if self.any_origin {
159			"\nany-origin: true"
160		} else {
161			""
162		};
163		println!(
164			"serving '{}' at {}{any_origin}",
165			self.dir.display(),
166			self.address().unwrap(),
167		);
168	}
169	pub fn address(&self) -> Result<Address> {
170		Ok(Address {
171			host: Address::host_from_str(&self.host)?,
172			port: self.port.parse::<u16>()?,
173			secure: self.secure,
174			..Default::default()
175		})
176	}
177}
178
179async fn ping(req: Request) -> Response<String> {
180	let body = format!("request was {:?}", req);
181	Response::new(body)
182}