Fixes #279 switch from iron to actix-web.

This implementation seems to match the behavior of the previous iron
server.

Static files are rendered as-is, and when a directory is requested, we
attempt to render a `index.html` found inside, or 404 if it's not
present.

The actix docs mention using channels to send a message to the server to
shut it down gracefully while running in another thread (as we're doing
here), but it seems like there would have to be some reorganization in
order to manage this effectively, perhaps holding the channel sender inside
`main.rs` so we can push a message through to the server when the call
to `cmd::serve()` finally returns.

For the time being, I left things without any careful attempts to
cleanup the server thread. This more or less matches the old iron
implementation as far as I can see.

The static file handling in actix is _just_ a little off from what we'd
want.
I left some comments in the source regarding why we can't just use their hook
for directory index redirection.
This commit is contained in:
Owen Nelson 2018-05-26 22:19:01 -07:00 committed by Vincent Prouillet
parent 8e8cdfeb7f
commit 908f16855a
4 changed files with 872 additions and 121 deletions

919
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -24,9 +24,8 @@ term-painter = "0.2"
# Used in init to ensure the url given as base_url is a valid one
url = "1.5"
# Below is for the serve cmd
staticfile = "0.5"
iron = "0.6"
mount = "0.4"
actix = "0.5"
actix-web = "0.6"
notify = "4"
ws = "0.7"
ctrlc = "3"

View file

@ -23,15 +23,15 @@
use std::env;
use std::fs::remove_dir_all;
use std::path::Path;
use std::io;
use std::path::{Path, PathBuf};
use std::sync::mpsc::channel;
use std::time::{Instant, Duration};
use std::thread;
use chrono::prelude::*;
use iron::{Iron, Request, IronResult, Response, status};
use mount::Mount;
use staticfile::Static;
use actix;
use actix_web::{fs, server, App, HttpRequest, HttpResponse, Responder};
use notify::{Watcher, RecursiveMode, watcher};
use ws::{WebSocket, Sender, Message};
use ctrlc;
@ -59,8 +59,8 @@ enum ChangeKind {
const LIVE_RELOAD: &'static str = include_str!("livereload.js");
fn livereload_handler(_: &mut Request) -> IronResult<Response> {
Ok(Response::with((status::Ok, LIVE_RELOAD.to_string())))
fn livereload_handler(_: HttpRequest) -> &'static str {
LIVE_RELOAD
}
fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &str) {
@ -102,6 +102,24 @@ fn create_new_site(interface: &str, port: &str, output_dir: &str, base_url: &str
Ok((site, address))
}
/// Attempt to render `index.html` when a directory is requested.
///
/// The default "batteries included" mechanisms for actix to handle directory
/// listings rely on redirection which behaves oddly (the location headers
/// seem to use relative paths for some reason).
/// They also mean that the address in the browser will include the
/// `index.html` on a successful redirect (rare), which is unsightly.
///
/// Rather than deal with all of that, we can hijack a hook for presenting a
/// custom directory listing response and serve it up using their
/// `NamedFile` responder.
fn handle_directory<'a, 'b>(dir: &'a fs::Directory, req: &'b HttpRequest) -> io::Result<HttpResponse> {
let mut path = PathBuf::from(&dir.base);
path.push(&dir.path);
path.push("index.html");
Ok(fs::NamedFile::open(path).respond_to(req).unwrap())
}
pub fn serve(interface: &str, port: &str, output_dir: &str, base_url: &str, config_file: &str) -> Result<()> {
let start = Instant::now();
let (mut site, address) = create_new_site(interface, port, output_dir, base_url, config_file)?;
@ -128,15 +146,28 @@ pub fn serve(interface: &str, port: &str, output_dir: &str, base_url: &str, conf
let _ = watcher.watch("sass/", RecursiveMode::Recursive);
let ws_address = format!("{}:{}", interface, site.live_reload.unwrap());
let output_path = Path::new(output_dir).to_path_buf();
// Start a webserver that serves the `output_dir` directory
let mut mount = Mount::new();
mount.mount("/", Static::new(Path::new(output_dir)));
mount.mount("/livereload.js", livereload_handler);
// Starts with a _ to not trigger the unused lint
// we need to assign to a variable otherwise it will block
let _iron = Iron::new(mount).http(address.as_str())
.chain_err(|| "Can't start the webserver")?;
// output path is going to need to be moved later on, so clone it for the
// http closure to avoid contention.
let static_root = output_path.clone();
thread::spawn(move || {
let sys = actix::System::new("http-server");
server::new(move || {
App::new()
.resource(r"/livereload.js", |r| r.f(livereload_handler))
// Start a webserver that serves the `output_dir` directory
.handler(r"/", fs::StaticFiles::new(&static_root)
.show_files_listing()
.files_listing_renderer(handle_directory))
})
.bind(&address)
.expect("Can't start the webserver")
.shutdown_timeout(20)
.start();
println!("Web server is available at http://{}", &address);
let _ = sys.run();
});
// The websocket for livereload
let ws_server = WebSocket::new(|output: Sender| {
@ -169,10 +200,9 @@ pub fn serve(interface: &str, port: &str, output_dir: &str, base_url: &str, conf
}
println!("Listening for changes in {}/{{{}}}", pwd, watchers.join(", "));
println!("Web server is available at http://{}", address);
println!("Press Ctrl+C to stop\n");
// Delete the output folder on ctrl+C
let output_path = Path::new(output_dir).to_path_buf();
ctrlc::set_handler(move || {
remove_dir_all(&output_path).expect("Failed to delete output directory");
::std::process::exit(0);

View file

@ -1,11 +1,10 @@
extern crate actix;
extern crate actix_web;
#[macro_use]
extern crate clap;
extern crate chrono;
extern crate term_painter;
extern crate staticfile;
extern crate iron;
extern crate mount;
extern crate notify;
extern crate term_painter;
extern crate url;
extern crate ws;
extern crate ctrlc;