Migrate serve command to hyper (#904)
hyper is already included in Zola due to the reqwest dependency (used in the link_checker and templates components). Replacing Actix with hyper in the serve command reduces the number of dependencies and slightly improves build times and binary size.
This commit is contained in:
parent
622b0f2965
commit
2966adbe4e
887
Cargo.lock
generated
887
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -26,8 +26,9 @@ termcolor = "1.0.4"
|
|||
# Used in init to ensure the url given as base_url is a valid one
|
||||
url = "2"
|
||||
# Below is for the serve cmd
|
||||
actix-files = "0.1"
|
||||
actix-web = { version = "1.0", default-features = false, features = [] }
|
||||
hyper = { version = "0.13", default-features = false, features = ["runtime"] }
|
||||
hyper-staticfile = "0.5"
|
||||
tokio = { version = "0.2", default-features = false, features = [] }
|
||||
notify = "4"
|
||||
ws = "0.9"
|
||||
ctrlc = "3"
|
||||
|
|
133
src/cmd/serve.rs
133
src/cmd/serve.rs
|
@ -22,16 +22,18 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
use std::env;
|
||||
use std::fs::{read_dir, remove_dir_all, File};
|
||||
use std::io::Read;
|
||||
use std::fs::{read_dir, remove_dir_all};
|
||||
use std::path::{Path, PathBuf, MAIN_SEPARATOR};
|
||||
use std::sync::mpsc::channel;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use actix_files as fs;
|
||||
use actix_web::middleware::errhandlers::{ErrorHandlerResponse, ErrorHandlers};
|
||||
use actix_web::{dev, http, web, App, HttpResponse, HttpServer};
|
||||
use hyper::header;
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Method, Request, Response, Server, StatusCode};
|
||||
use hyper_staticfile::ResolveResult;
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
use chrono::prelude::*;
|
||||
use ctrlc;
|
||||
use notify::{watcher, RecursiveMode, Watcher};
|
||||
|
@ -56,34 +58,79 @@ enum ChangeKind {
|
|||
Config,
|
||||
}
|
||||
|
||||
static INTERNAL_SERVER_ERROR_TEXT: &[u8] = b"Internal Server Error";
|
||||
static METHOD_NOT_ALLOWED_TEXT: &[u8] = b"Method Not Allowed";
|
||||
static NOT_FOUND_TEXT: &[u8] = b"Not Found";
|
||||
|
||||
// This is dist/livereload.min.js from the LiveReload.js v3.1.0 release
|
||||
const LIVE_RELOAD: &str = include_str!("livereload.js");
|
||||
|
||||
struct ErrorFilePaths {
|
||||
not_found: PathBuf,
|
||||
async fn handle_request(req: Request<Body>, root: PathBuf) -> Result<Response<Body>> {
|
||||
// livereload.js is served using the LIVE_RELOAD str, not a file
|
||||
if req.uri().path() == "/livereload.js" {
|
||||
if req.method() == Method::GET {
|
||||
return Ok(livereload_js());
|
||||
} else {
|
||||
return Ok(method_not_allowed());
|
||||
}
|
||||
}
|
||||
|
||||
fn not_found<B>(
|
||||
res: dev::ServiceResponse<B>,
|
||||
) -> std::result::Result<ErrorHandlerResponse<B>, actix_web::Error> {
|
||||
let buf: Vec<u8> = {
|
||||
let error_files: &ErrorFilePaths = res.request().app_data().unwrap();
|
||||
|
||||
let mut fh = File::open(&error_files.not_found)?;
|
||||
let mut buf: Vec<u8> = vec![];
|
||||
let _ = fh.read_to_end(&mut buf)?;
|
||||
buf
|
||||
let result = hyper_staticfile::resolve(&root, &req).await.unwrap();
|
||||
match result {
|
||||
ResolveResult::MethodNotMatched => return Ok(method_not_allowed()),
|
||||
ResolveResult::NotFound | ResolveResult::UriNotMatched => {
|
||||
return Ok(not_found(Path::new(&root.join("404.html"))).await)
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
let new_resp = HttpResponse::build(http::StatusCode::NOT_FOUND)
|
||||
.header(http::header::CONTENT_TYPE, http::header::HeaderValue::from_static("text/html"))
|
||||
.body(buf);
|
||||
|
||||
Ok(ErrorHandlerResponse::Response(res.into_response(new_resp.into_body())))
|
||||
Ok(hyper_staticfile::ResponseBuilder::new().request(&req).build(result).unwrap())
|
||||
}
|
||||
|
||||
fn livereload_handler() -> HttpResponse {
|
||||
HttpResponse::Ok().content_type("text/javascript").body(LIVE_RELOAD)
|
||||
fn livereload_js() -> Response<Body> {
|
||||
Response::builder()
|
||||
.header(header::CONTENT_TYPE, "text/javascript")
|
||||
.status(StatusCode::OK)
|
||||
.body(LIVE_RELOAD.into())
|
||||
.expect("Could not build livereload.js response")
|
||||
}
|
||||
|
||||
fn internal_server_error() -> Response<Body> {
|
||||
Response::builder()
|
||||
.header(header::CONTENT_TYPE, "text/plain")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(INTERNAL_SERVER_ERROR_TEXT.into())
|
||||
.expect("Could not build Internal Server Error response")
|
||||
}
|
||||
|
||||
fn method_not_allowed() -> Response<Body> {
|
||||
Response::builder()
|
||||
.header(header::CONTENT_TYPE, "text/plain")
|
||||
.status(StatusCode::METHOD_NOT_ALLOWED)
|
||||
.body(METHOD_NOT_ALLOWED_TEXT.into())
|
||||
.expect("Could not build Method Not Allowed response")
|
||||
}
|
||||
|
||||
async fn not_found(page_path: &Path) -> Response<Body> {
|
||||
if let Ok(mut file) = tokio::fs::File::open(page_path).await {
|
||||
let mut buf = Vec::new();
|
||||
if file.read_to_end(&mut buf).await.is_ok() {
|
||||
return Response::builder()
|
||||
.header(header::CONTENT_TYPE, "text/html")
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(buf.into())
|
||||
.expect("Could not build Not Found response");
|
||||
}
|
||||
|
||||
return internal_server_error();
|
||||
}
|
||||
|
||||
// Use a plain text response when page_path isn't available
|
||||
Response::builder()
|
||||
.header(header::CONTENT_TYPE, "text/plain")
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(NOT_FOUND_TEXT.into())
|
||||
.expect("Could not build Not Found response")
|
||||
}
|
||||
|
||||
fn rebuild_done_handling(broadcaster: &Option<Sender>, res: Result<()>, reload_path: &str) {
|
||||
|
@ -202,28 +249,38 @@ pub fn serve(
|
|||
let static_root = output_path.clone();
|
||||
let broadcaster = if !watch_only {
|
||||
thread::spawn(move || {
|
||||
let s = HttpServer::new(move || {
|
||||
let error_handlers =
|
||||
ErrorHandlers::new().handler(http::StatusCode::NOT_FOUND, not_found);
|
||||
let addr = address.parse().unwrap();
|
||||
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.enable_all()
|
||||
.basic_scheduler()
|
||||
.build()
|
||||
.expect("Could not build tokio runtime");
|
||||
|
||||
rt.block_on(async {
|
||||
let make_service = make_service_fn(move |_| {
|
||||
let static_root = static_root.clone();
|
||||
|
||||
async {
|
||||
Ok::<_, hyper::Error>(service_fn(move |req| {
|
||||
handle_request(req, static_root.clone())
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
let server = Server::bind(&addr).serve(make_service);
|
||||
|
||||
App::new()
|
||||
.data(ErrorFilePaths { not_found: static_root.join("404.html") })
|
||||
.wrap(error_handlers)
|
||||
.route("/livereload.js", web::get().to(livereload_handler))
|
||||
// Start a webserver that serves the `output_dir` directory
|
||||
.service(fs::Files::new("/", &static_root).index_file("index.html"))
|
||||
})
|
||||
.bind(&address)
|
||||
.expect("Can't start the webserver")
|
||||
.shutdown_timeout(20);
|
||||
println!("Web server is available at http://{}\n", &address);
|
||||
if open {
|
||||
if let Err(err) = open::that(format!("http://{}", &address)) {
|
||||
eprintln!("Failed to open URL in your browser: {}", err);
|
||||
}
|
||||
}
|
||||
s.run()
|
||||
|
||||
server.await.expect("Could not start web server");
|
||||
});
|
||||
});
|
||||
|
||||
// The websocket for livereload
|
||||
let ws_server = WebSocket::new(|output: Sender| {
|
||||
move |msg: Message| {
|
||||
|
|
Loading…
Reference in a new issue