tower_livereload/
lib.rs

1//! A middleware for browser reloading, built on top of [`tower`].
2//!
3//! # Example
4//!
5//! Note that [`axum`] is only used as an example here, pretty much any Rust
6//! HTTP library or framework will be compatible!
7//!
8//! ```
9//! use axum::{response::Html, routing::get, Router};
10//! use tower_livereload::LiveReloadLayer;
11//!
12//! #[tokio::main]
13//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
14//!     let app = Router::new()
15//!         .route("/", get(|| async { Html("<h1>Wow, such webdev</h1>") }))
16//!         .layer(LiveReloadLayer::new());
17//!
18//!     let listener = tokio::net::TcpListener::bind("0.0.0.0:3030").await?;
19//!     axum::serve(listener, app).await?;
20//!
21//!     Ok(())
22//! }
23//! ```
24//!
25//! If you continuously rebuild and rerun this example e.g. using [`watchexec`],
26//! you should see your browser reload whenever the code is changed.
27//!
28//! More examples can be found on GitHub under [examples].
29//!
30//! [`axum`]: https://docs.rs/axum
31//! [`tower`]: https://docs.rs/tower
32//! [`watchexec`]: https://watchexec.github.io/
33//! [examples]: https://github.com/leotaku/tower-livereload/tree/master/examples
34//!
35//! # Manual reload
36//!
37//! With the [`Reloader`] utility, it is possible to reload your web browser
38//! entirely using hooks from Rust code. See this [example] on GitHub for
39//! pointers on how to implement a self-contained live-reloading static server.
40//!
41//! [example]: https://github.com/leotaku/tower-livereload/blob/master/examples/axum-file-watch/
42//!
43//! # Ecosystem compatibility
44//!
45//! `tower-livereload` has been built from the ground up to provide the highest
46//! amount of ecosystem compatibility.
47//!
48//! The provided middleware uses the [`http`] and [`http_body`] crates as its
49//! HTTP abstractions. That means it is compatible with any library or framework
50//! that also uses those crates, such as [`hyper`], [`axum`], [`tonic`], and
51//! [`warp`].
52//!
53//! [`http`]: https://docs.rs/http
54//! [`http_body`]: https://docs.rs/http_body
55//! [`hyper`]: https://docs.rs/hyper
56//! [`axum`]: https://docs.rs/axum
57//! [`tonic`]: https://docs.rs/tonic
58//! [`warp`]: https://docs.rs/warp
59//!
60//! # Heuristics
61//!
62//! To provide LiveReload functionality, we have to inject code into HTML web
63//! pages. To determine whether a page is injectable, some header-based
64//! heuristics are used. In particular, [`Content-Type`] has to start with
65//! `text/html` and [`Content-Encoding`] must not be set.
66//!
67//! If LiveReload is not working for some of your pages, ensure that these
68//! heuristics apply to your responses. In particular, if you use middleware to
69//! compress your HTML, ensure that the [`LiveReload`] middleware is
70//! applied before your compression middleware.
71//!
72//! [`Content-Type`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
73//! [`Content-Encoding`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
74
75#![forbid(unsafe_code, unused_unsafe)]
76#![warn(clippy::all, missing_docs, nonstandard_style, future_incompatible)]
77#![allow(clippy::type_complexity)]
78
79mod inject;
80mod long_poll;
81mod overlay;
82pub mod predicate;
83
84use std::{convert::Infallible, time::Duration};
85
86use http::{header, Request, Response, StatusCode};
87use inject::InjectService;
88use long_poll::LongPollBody;
89use overlay::OverlayService;
90use predicate::{Always, ContentTypeStartsWith, Predicate};
91use tokio::sync::broadcast::Sender;
92use tower::{Layer, Service};
93
94const DEFAULT_PREFIX: &str = "/tower-livereload/long-name-to-avoid-collisions";
95
96/// Utility to send reload requests to clients.
97#[derive(Clone, Debug)]
98pub struct Reloader {
99    sender: Sender<()>,
100}
101
102impl Reloader {
103    /// Create a new [`Reloader`].
104    ///
105    /// This can be manually passed to the [`LiveReload`] constructor, but in
106    /// most cases the [`LiveReloadLayer`] and [`LiveReloadLayer::reloader`]
107    /// utilities are preferred.
108    pub fn new() -> Self {
109        let (sender, _) = tokio::sync::broadcast::channel(1);
110        Self { sender }
111    }
112
113    /// Send a reload request to all open clients.
114    pub fn reload(&self) {
115        self.sender.send(()).ok();
116    }
117}
118
119impl Default for Reloader {
120    fn default() -> Self {
121        Self::new()
122    }
123}
124
125/// Layer to apply [`LiveReload`] middleware.
126#[derive(Clone, Debug)]
127pub struct LiveReloadLayer<ReqPred = Always, ResPred = ContentTypeStartsWith<&'static str>> {
128    custom_prefix: Option<String>,
129    reloader: Reloader,
130    req_predicate: ReqPred,
131    res_predicate: ResPred,
132    reload_interval: Duration,
133}
134
135impl LiveReloadLayer {
136    /// Create a new [`LiveReloadLayer`] with the default prefix for internal
137    /// routes.
138    ///
139    /// The default prefix is deliberately long and specific to avoid any
140    /// accidental collisions with the wrapped service.
141    pub fn new() -> Self {
142        Self {
143            custom_prefix: None,
144            reloader: Reloader::new(),
145            req_predicate: Always,
146            res_predicate: ContentTypeStartsWith::new("text/html"),
147            reload_interval: Duration::from_secs(1),
148        }
149    }
150
151    /// Create a new [`LiveReloadLayer`] with a custom prefix.
152    #[deprecated(
153        since = "0.8.0",
154        note = "please use `LiveReloadLayer::new` and `custom_prefix` instead"
155    )]
156    pub fn with_custom_prefix<P: Into<String>>(prefix: P) -> Self {
157        Self::new().custom_prefix(prefix)
158    }
159}
160
161impl<ReqPred, ResPred> LiveReloadLayer<ReqPred, ResPred> {
162    /// Set a custom prefix for internal routes for the given
163    /// [`LiveReloadLayer`].
164    pub fn custom_prefix<P: Into<String>>(self, prefix: P) -> Self {
165        Self {
166            custom_prefix: Some(prefix.into()),
167            ..self
168        }
169    }
170
171    /// Set a custom predicate for requests that should have their response HTML
172    /// injected with live-reload logic.
173    ///
174    /// Note that this predicate is applied in addition to the default response
175    /// predicate, which makes sure that only HTML responses are injected.
176    ///
177    /// Also see [`predicate`] for pre-defined predicates and
178    /// [`predicate::Predicate`] for how to implement your own predicates.
179    pub fn request_predicate<Body, P: Predicate<Request<Body>>>(
180        self,
181        predicate: P,
182    ) -> LiveReloadLayer<P, ResPred> {
183        LiveReloadLayer {
184            custom_prefix: self.custom_prefix,
185            reloader: self.reloader,
186            req_predicate: predicate,
187            res_predicate: self.res_predicate,
188            reload_interval: self.reload_interval,
189        }
190    }
191
192    /// Set a custom predicate for responses that should be injected with
193    /// live-reload logic.
194    ///
195    /// Note that this predicate is applied instead of the default response
196    /// predicate, which would make sure that only HTML responses are injected.
197    /// However, even with a custom predicate only responses without a custom
198    /// encoding i.e. no [`Content-Encoding`] header can and will be injected.
199    ///
200    /// Also see [`predicate`] for pre-defined predicates and
201    /// [`predicate::Predicate`] for how to implement your own predicates.
202    ///
203    /// [`Content-Encoding`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
204    pub fn response_predicate<Body, P: Predicate<Response<Body>>>(
205        self,
206        predicate: P,
207    ) -> LiveReloadLayer<ReqPred, P> {
208        LiveReloadLayer {
209            custom_prefix: self.custom_prefix,
210            reloader: self.reloader,
211            req_predicate: self.req_predicate,
212            res_predicate: predicate,
213            reload_interval: self.reload_interval,
214        }
215    }
216
217    /// Set a custom retry interval for the live-reload logic.
218    pub fn reload_interval(self, interval: Duration) -> Self {
219        Self {
220            reload_interval: interval,
221            ..self
222        }
223    }
224
225    /// Return a manual [`Reloader`] trigger for the given [`LiveReloadLayer`].
226    pub fn reloader(&self) -> Reloader {
227        self.reloader.clone()
228    }
229}
230
231impl Default for LiveReloadLayer {
232    fn default() -> Self {
233        Self::new()
234    }
235}
236
237impl<S, ReqPred: Copy, ResPred: Copy> Layer<S> for LiveReloadLayer<ReqPred, ResPred> {
238    type Service = LiveReload<S, ReqPred, ResPred>;
239
240    fn layer(&self, inner: S) -> Self::Service {
241        #[allow(deprecated)]
242        LiveReload::new(
243            inner,
244            self.reloader.clone(),
245            self.req_predicate,
246            self.res_predicate,
247            self.reload_interval,
248            self.custom_prefix
249                .as_ref()
250                .cloned()
251                .unwrap_or_else(|| DEFAULT_PREFIX.to_owned()),
252        )
253    }
254}
255
256type InnerService<S, ReqPred, ResPred> = OverlayService<
257    String,
258    Infallible,
259    OverlayService<LongPollBody, Infallible, InjectService<S, ReqPred, ResPred>>,
260>;
261
262/// Middleware to enable LiveReload functionality.
263#[derive(Clone, Debug)]
264pub struct LiveReload<S, ReqPred = Always, ResPred = ContentTypeStartsWith<&'static str>> {
265    service: InnerService<S, ReqPred, ResPred>,
266}
267
268impl<S, ReqPred, ResPred> LiveReload<S, ReqPred, ResPred> {
269    #[deprecated(
270        since = "0.9.0",
271        note = "please use `LiveReloadLayer::new().layer(service)` instead"
272    )]
273    /// Create a new [`LiveReload`] middleware.
274    pub fn new<P: Into<String>>(
275        service: S,
276        reloader: Reloader,
277        req_predicate: ReqPred,
278        res_predicate: ResPred,
279        reload_interval: Duration,
280        prefix: P,
281    ) -> Self {
282        let prefix = prefix.into();
283        let long_poll_path = format!("{}/long-poll", prefix);
284        let back_up_path = format!("{}/back-up", prefix);
285        let inject = InjectService::new(
286            service,
287            format!(
288                r#"<script data-long-poll="{long_poll}" data-back-up="{back_up}" data-reload-interval="{reload_interval}">{code}</script>"#,
289                code = include_str!("../assets/polling.js"),
290                long_poll = long_poll_path,
291                back_up = back_up_path,
292                reload_interval = reload_interval.as_millis(),
293            )
294            .into(),
295            req_predicate,
296            res_predicate,
297        );
298        let overlay_poll = OverlayService::new(inject).path(long_poll_path, move || {
299            Response::builder()
300                .status(StatusCode::OK)
301                .header(header::CONTENT_TYPE, "text/event-stream")
302                .body(LongPollBody::new(reloader.sender.subscribe()))
303                .map_err(|_| unreachable!())
304        });
305        let overlay_up = OverlayService::new(overlay_poll).path(back_up_path, || {
306            Response::builder()
307                .status(StatusCode::OK)
308                .header(header::CONTENT_TYPE, "text/plain")
309                .body("Ok".to_owned())
310                .map_err(|_| unreachable!())
311        });
312
313        LiveReload {
314            service: overlay_up,
315        }
316    }
317}
318
319impl<ReqBody, ResBody, S, ReqPred, ResPred> Service<Request<ReqBody>>
320    for LiveReload<S, ReqPred, ResPred>
321where
322    S: Service<Request<ReqBody>, Response = Response<ResBody>>,
323    ResBody: http_body::Body,
324    ReqPred: Predicate<Request<ReqBody>>,
325    ResPred: Predicate<Response<ResBody>>,
326{
327    type Response = <InnerService<S, ReqPred, ResPred> as Service<Request<ReqBody>>>::Response;
328    type Error = <InnerService<S, ReqPred, ResPred> as Service<Request<ReqBody>>>::Error;
329    type Future = <InnerService<S, ReqPred, ResPred> as Service<Request<ReqBody>>>::Future;
330
331    fn poll_ready(
332        &mut self,
333        cx: &mut std::task::Context<'_>,
334    ) -> std::task::Poll<Result<(), Self::Error>> {
335        self.service.poll_ready(cx)
336    }
337
338    fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
339        self.service.call(req)
340    }
341}