1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
//! Launch a local network server with live reload feature for static pages.
//!
//! ## Create live server
//! ```
//! use live_server::listen;
//!
//! async fn serve() -> Result<(), Box<dyn std::error::Error>> {
//! listen("127.0.0.1:8080", "./").await?.start().await
//! }
//! ```
//!
//! ## Enable logs (Optional)
//! ```rust
//! env_logger::init();
//! ```
mod server;
mod watcher;
use std::{error::Error, net::IpAddr, path::PathBuf};
use axum::Router;
use local_ip_address::local_ip;
use notify::RecommendedWatcher;
use notify_debouncer_full::{DebouncedEvent, Debouncer, FileIdMap};
use server::{create_listener, create_server};
use tokio::{
net::TcpListener,
sync::{broadcast, mpsc::Receiver, OnceCell},
};
use watcher::create_watcher;
static ADDR: OnceCell<String> = OnceCell::const_new();
static ROOT: OnceCell<PathBuf> = OnceCell::const_new();
static HARD: OnceCell<bool> = OnceCell::const_new();
static TX: OnceCell<broadcast::Sender<()>> = OnceCell::const_new();
pub struct Listener {
tcp_listener: TcpListener,
router: Router,
root_path: PathBuf,
debouncer: Debouncer<RecommendedWatcher, FileIdMap>,
rx: Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>,
hard: bool,
}
impl Listener {
/// Start live-server.
///
/// ```
/// use live_server::listen;
///
/// async fn serve() -> Result<(), Box<dyn std::error::Error>> {
/// listen("127.0.0.1:8080", "./").await?.start().await
/// }
/// ```
pub async fn start(self) -> Result<(), Box<dyn Error>> {
HARD.set(self.hard)?;
ROOT.set(self.root_path.clone())?;
let (tx, _) = broadcast::channel(16);
TX.set(tx)?;
let watcher_future = tokio::spawn(watcher::watch(self.root_path, self.debouncer, self.rx));
let server_future = tokio::spawn(server::serve(self.tcp_listener, self.router));
tokio::try_join!(watcher_future, server_future)?;
Ok(())
}
/// Always hard reload the page instead of hot-reload
/// ```
/// use live_server::listen;
///
/// async fn serve_hard() -> Result<(), Box<dyn std::error::Error>> {
/// listen("127.0.0.1:8080", "./").await?.hard_reload().start().await
/// }
/// ```
pub fn hard_reload(mut self) -> Self {
self.hard = true;
self
}
/// Return the link of the server, like `http://127.0.0.1:8080`.
///
/// ```
/// use live_server::listen;
///
/// async fn serve() {
/// let listener = listen("127.0.0.1:8080", "./").await.unwrap();
/// let link = listener.link().unwrap();
/// assert_eq!(link, "http://127.0.0.1:8080");
/// }
/// ```
///
/// This is useful when you did not specify the host or port (e.g. `listen("0.0.0.0:0", ".")`),
/// because this method will return the specific address.
pub fn link(&self) -> Result<String, Box<dyn Error>> {
let addr = self.tcp_listener.local_addr()?;
let port = addr.port();
let host = addr.ip();
let host = match host.is_unspecified() {
true => local_ip()?,
false => host,
};
Ok(match host {
IpAddr::V4(host) => format!("http://{host}:{port}"),
IpAddr::V6(host) => format!("http://[{host}]:{port}"),
})
}
}
/// Create live-server listener
///
/// ```
/// use live_server::listen;
///
/// async fn serve() -> Result<(), Box<dyn std::error::Error>> {
/// listen("127.0.0.1:8080", "./").await?.start().await
/// }
/// ```
pub async fn listen<A: Into<String>, R: Into<PathBuf>>(
addr: A,
root: R,
) -> Result<Listener, String> {
let tcp_listener = create_listener(addr.into()).await?;
let router = create_server();
let (debouncer, root_path, rx) = create_watcher(root.into()).await?;
Ok(Listener {
tcp_listener,
router,
debouncer,
root_path,
rx,
hard: false,
})
}