diff --git a/src/archives/mod.rs b/src/archives/mod.rs index d029173..f118420 100644 --- a/src/archives/mod.rs +++ b/src/archives/mod.rs @@ -94,7 +94,8 @@ pub fn read_archive<'a>(name: &str, data: &'a [u8]) -> Result + // Decompress a .tar.xz file let mut decompresser = XzDecoder::new(data); let mut decompressed_data = Vec::new(); - decompresser.read_to_end(&mut decompressed_data) + decompresser + .read_to_end(&mut decompressed_data) .map_err(|x| format!("Failed to decompress data: {:?}", x))?; let decompressed_contents: Box = Box::new(Cursor::new(decompressed_data)); diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs new file mode 100644 index 0000000..8788a3e --- /dev/null +++ b/src/frontend/mod.rs @@ -0,0 +1,27 @@ +//! Provides the frontend interface, including HTTP server. + +use std::sync::{Arc, RwLock}; + +use installer::InstallerFramework; +use logging::LoggingErrors; + +mod rest; +mod ui; + +/// Launches the main web server + UI. Returns when the framework has been consumed + web UI closed. +pub fn launch(app_name: &str, is_launcher: bool, framework: InstallerFramework) { + let framework = Arc::new(RwLock::new(framework)); + + let (servers, address) = rest::server::spawn_servers(framework.clone()); + + ui::start_ui(app_name, &address, is_launcher); + + // Explicitly hint that we want the servers instance until here. + drop(servers); + + framework + .write() + .log_expect("Failed to write to framework to finalize") + .shutdown() + .log_expect("Failed to finalize framework"); +} diff --git a/src/frontend/rest/mod.rs b/src/frontend/rest/mod.rs new file mode 100644 index 0000000..4563295 --- /dev/null +++ b/src/frontend/rest/mod.rs @@ -0,0 +1,4 @@ +//! Contains the main web server used within the application. + +pub mod server; +mod services; diff --git a/src/frontend/rest/server.rs b/src/frontend/rest/server.rs new file mode 100644 index 0000000..ef860e3 --- /dev/null +++ b/src/frontend/rest/server.rs @@ -0,0 +1,84 @@ +//! Contains the over-arching server object + methods to manipulate it. + +use frontend::rest::services::WebService; + +use installer::InstallerFramework; + +use logging::LoggingErrors; + +use hyper::server::Http; + +use std::sync::{Arc, RwLock}; + +use std::net::{SocketAddr, TcpListener, ToSocketAddrs}; + +use std::thread; +use std::thread::JoinHandle; + +/// Acts as a communication mechanism between the Hyper WebService and the rest of the +/// application. +pub struct WebServer { + _handle: JoinHandle<()>, +} + +impl WebServer { + /// Creates a new web server with the specified address. + pub fn with_addr( + framework: Arc>, + addr: SocketAddr, + ) -> Result { + let handle = thread::spawn(move || { + let server = Http::new() + .bind(&addr, move || Ok(WebService::new(framework.clone()))) + .log_expect("Failed to bind to port"); + + server.run().log_expect("Failed to run HTTP server"); + }); + + Ok(WebServer { _handle: handle }) + } +} + +/// Spawns a server instance on all local interfaces. +/// +/// Returns server instances + http address of service running. +pub fn spawn_servers(framework: Arc>) -> (Vec, String) { + // Firstly, allocate us an epidermal port + let target_port = { + let listener = TcpListener::bind("127.0.0.1:0") + .log_expect("At least one local address should be free"); + listener + .local_addr() + .log_expect("Should be able to pull address from listener") + .port() + }; + + // Now, iterate over all ports + let addresses = "localhost:0" + .to_socket_addrs() + .log_expect("No localhost address found"); + + let mut instances = Vec::with_capacity(addresses.len()); + let mut http_address = None; + + // Startup HTTP server for handling the web view + for mut address in addresses { + address.set_port(target_port); + + let server = WebServer::with_addr(framework.clone(), address) + .log_expect("Failed to bind to address"); + + info!("Spawning server instance @ {:?}", address); + + http_address = Some(address); + + instances.push(server); + } + + let http_address = http_address.log_expect("No HTTP address found"); + + ( + instances, + format!("http://localhost:{}", http_address.port()), + ) +} diff --git a/src/frontend/rest/services/attributes.rs b/src/frontend/rest/services/attributes.rs new file mode 100644 index 0000000..ef3063d --- /dev/null +++ b/src/frontend/rest/services/attributes.rs @@ -0,0 +1,31 @@ +//! The /api/attr call returns an executable script containing session variables. + +use frontend::rest::services::default_future; +use frontend::rest::services::encapsulate_json; +use frontend::rest::services::Future; +use frontend::rest::services::Request; +use frontend::rest::services::Response; +use frontend::rest::services::WebService; + +use hyper::header::{ContentLength, ContentType}; + +use logging::LoggingErrors; + +pub fn handle(service: &WebService, _req: Request) -> Future { + let framework = service.get_framework_read(); + + let file = encapsulate_json( + "base_attributes", + &framework + .base_attributes + .to_json_str() + .log_expect("Failed to render JSON representation of config"), + ); + + default_future( + Response::new() + .with_header(ContentLength(file.len() as u64)) + .with_header(ContentType::json()) + .with_body(file), + ) +} diff --git a/src/frontend/rest/services/config.rs b/src/frontend/rest/services/config.rs new file mode 100644 index 0000000..3dc3e16 --- /dev/null +++ b/src/frontend/rest/services/config.rs @@ -0,0 +1,72 @@ +//! The /api/config call returns the current installer framework configuration. +//! +//! This endpoint should be usable directly from a