mirror of
https://github.com/getzola/zola
synced 2024-12-13 13:52:28 +00:00
Simple console error message dialog in zola serve
webpages (#2312)
* first attempt at injecting HTML into outgoing webpages * finish error handling * fix test_site debug line
This commit is contained in:
parent
a49af5fa48
commit
6a612ca388
1 changed files with 88 additions and 4 deletions
|
@ -21,16 +21,20 @@
|
||||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
use std::cell::Cell;
|
||||||
use std::fs::read_dir;
|
use std::fs::read_dir;
|
||||||
|
use std::future::IntoFuture;
|
||||||
use std::net::{SocketAddrV4, TcpListener};
|
use std::net::{SocketAddrV4, TcpListener};
|
||||||
use std::path::{Path, PathBuf, MAIN_SEPARATOR};
|
use std::path::{Path, PathBuf, MAIN_SEPARATOR};
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
|
use std::sync::Mutex;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use hyper::header;
|
use hyper::http::HeaderValue;
|
||||||
use hyper::server::Server;
|
use hyper::server::Server;
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::service::{make_service_fn, service_fn};
|
||||||
|
use hyper::{body, header};
|
||||||
use hyper::{Body, Method, Request, Response, StatusCode};
|
use hyper::{Body, Method, Request, Response, StatusCode};
|
||||||
use mime_guess::from_path as mimetype_from_path;
|
use mime_guess::from_path as mimetype_from_path;
|
||||||
use time::macros::format_description;
|
use time::macros::format_description;
|
||||||
|
@ -75,6 +79,19 @@ static NOT_FOUND_TEXT: &[u8] = b"Not Found";
|
||||||
// This is dist/livereload.min.js from the LiveReload.js v3.2.4 release
|
// This is dist/livereload.min.js from the LiveReload.js v3.2.4 release
|
||||||
const LIVE_RELOAD: &str = include_str!("livereload.js");
|
const LIVE_RELOAD: &str = include_str!("livereload.js");
|
||||||
|
|
||||||
|
static SERVE_ERROR: Mutex<Cell<Option<(&'static str, errors::Error)>>> =
|
||||||
|
Mutex::new(Cell::new(None));
|
||||||
|
|
||||||
|
fn clear_serve_error() {
|
||||||
|
let _ = SERVE_ERROR.lock().map(|error| error.swap(&Cell::new(None)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_serve_error(msg: &'static str, e: errors::Error) {
|
||||||
|
if let Ok(serve_error) = SERVE_ERROR.lock() {
|
||||||
|
serve_error.swap(&Cell::new(Some((msg, e))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_request(req: Request<Body>, mut root: PathBuf) -> Result<Response<Body>> {
|
async fn handle_request(req: Request<Body>, mut root: PathBuf) -> Result<Response<Body>> {
|
||||||
let original_root = root.clone();
|
let original_root = root.clone();
|
||||||
let mut path = RelativePathBuf::new();
|
let mut path = RelativePathBuf::new();
|
||||||
|
@ -148,6 +165,61 @@ async fn handle_request(req: Request<Body>, mut root: PathBuf) -> Result<Respons
|
||||||
.unwrap())
|
.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inserts build error message boxes into HTML responses when needed.
|
||||||
|
async fn response_error_injector(
|
||||||
|
req: impl IntoFuture<Output = Result<Response<Body>>>,
|
||||||
|
) -> Result<Response<Body>> {
|
||||||
|
let req = req.await;
|
||||||
|
|
||||||
|
// return req as-is if the request is Err(), not HTML, or if there are no error messages.
|
||||||
|
if req
|
||||||
|
.as_ref()
|
||||||
|
.map(|req| {
|
||||||
|
req.headers()
|
||||||
|
.get(header::CONTENT_TYPE)
|
||||||
|
.map(|val| val != &HeaderValue::from_static("text/html"))
|
||||||
|
.unwrap_or(true)
|
||||||
|
})
|
||||||
|
.unwrap_or(true)
|
||||||
|
|| SERVE_ERROR.lock().unwrap().get_mut().is_none()
|
||||||
|
{
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut req = req.unwrap();
|
||||||
|
let mut bytes = body::to_bytes(req.body_mut()).await.unwrap().to_vec();
|
||||||
|
|
||||||
|
if let Some((msg, error)) = SERVE_ERROR.lock().unwrap().get_mut() {
|
||||||
|
// Generate an error message similar to the CLI version in messages::unravel_errors.
|
||||||
|
let mut error_str = String::new();
|
||||||
|
|
||||||
|
if !msg.is_empty() {
|
||||||
|
error_str.push_str(&format!("Error: {msg}\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
error_str.push_str(&format!("Error: {error}\n"));
|
||||||
|
|
||||||
|
let mut cause = error.source();
|
||||||
|
while let Some(e) = cause {
|
||||||
|
error_str.push_str(&format!("Reason: {}\n", e));
|
||||||
|
cause = e.source();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the error message (wrapped in an HTML dialog box) to the end of the HTML body.
|
||||||
|
//
|
||||||
|
// The message will be outside of <html> and <body> but web browsers are flexible enough
|
||||||
|
// that they will move it to the end of <body> at page load.
|
||||||
|
let html_error = format!(
|
||||||
|
r#"<div style="all:revert;position:fixed;display:flex;align-items:center;justify-content:center;background-color:rgb(0,0,0,0.5);top:0;right:0;bottom:0;left:0;"><div style="background-color:white;padding:0.5rem;border-radius:0.375rem;filter:drop-shadow(0,25px,25px,rgb(0,0,0/0.15));overflow-x:auto;"><p style="font-weight:700;color:black;font-size:1.25rem;margin:0;margin-bottom:0.5rem;">Zola Build Error:</p><pre style="padding:0.5rem;margin:0;border-radius:0.375rem;background-color:#363636;color:#CE4A2F;font-weight:700;">{error_str}</pre></div></div>"#
|
||||||
|
);
|
||||||
|
bytes.extend(html_error.as_bytes());
|
||||||
|
|
||||||
|
*req.body_mut() = Body::from(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(req)
|
||||||
|
}
|
||||||
|
|
||||||
fn livereload_js() -> Response<Body> {
|
fn livereload_js() -> Response<Body> {
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.header(header::CONTENT_TYPE, "text/javascript")
|
.header(header::CONTENT_TYPE, "text/javascript")
|
||||||
|
@ -213,6 +285,7 @@ fn not_found() -> Response<Body> {
|
||||||
fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &str) {
|
fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &str) {
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
clear_serve_error();
|
||||||
broadcaster
|
broadcaster
|
||||||
.send(format!(
|
.send(format!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -228,7 +301,12 @@ fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &st
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
Err(e) => messages::unravel_errors("Failed to build the site", &e),
|
Err(e) => {
|
||||||
|
let msg = "Failed to build the site";
|
||||||
|
|
||||||
|
messages::unravel_errors(msg, &e);
|
||||||
|
set_serve_error(msg, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,7 +462,7 @@ pub fn serve(
|
||||||
|
|
||||||
async {
|
async {
|
||||||
Ok::<_, hyper::Error>(service_fn(move |req| {
|
Ok::<_, hyper::Error>(service_fn(move |req| {
|
||||||
handle_request(req, static_root.clone())
|
response_error_injector(handle_request(req, static_root.clone()))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -514,11 +592,17 @@ pub fn serve(
|
||||||
ws_port,
|
ws_port,
|
||||||
) {
|
) {
|
||||||
Ok((s, _)) => {
|
Ok((s, _)) => {
|
||||||
|
clear_serve_error();
|
||||||
rebuild_done_handling(&broadcaster, Ok(()), "/x.js");
|
rebuild_done_handling(&broadcaster, Ok(()), "/x.js");
|
||||||
|
|
||||||
Some(s)
|
Some(s)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
messages::unravel_errors("Failed to build the site", &e);
|
let msg = "Failed to build the site";
|
||||||
|
|
||||||
|
messages::unravel_errors(msg, &e);
|
||||||
|
set_serve_error(msg, e);
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue