From 9d1f4c25765c50aa5a1946f8dff8321e769df954 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 23 Jun 2019 20:19:43 +1000 Subject: [PATCH] Break apart REST into separate services This cleans up locking, ensures consistent futures for all endpoints and enhances code re-use. --- src/archives/mod.rs | 3 +- src/frontend/mod.rs | 27 + src/frontend/rest/mod.rs | 4 + src/frontend/rest/server.rs | 84 ++++ src/frontend/rest/services/attributes.rs | 31 ++ src/frontend/rest/services/config.rs | 72 +++ src/frontend/rest/services/default_path.rs | 33 ++ src/frontend/rest/services/exit.rs | 30 ++ src/frontend/rest/services/install.rs | 68 +++ .../rest/services/installation_status.rs | 30 ++ src/frontend/rest/services/mod.rs | 146 ++++++ src/frontend/rest/services/packages.rs | 29 ++ src/frontend/rest/services/static_files.rs | 40 ++ src/frontend/rest/services/uninstall.rs | 32 ++ src/frontend/rest/services/update_updater.rs | 32 ++ src/frontend/ui/mod.rs | 75 +++ src/installer.rs | 36 +- src/main.rs | 123 +---- src/rest.rs | 474 ------------------ src/sources/github/mod.rs | 4 +- static/js/main.js | 6 +- 21 files changed, 783 insertions(+), 596 deletions(-) create mode 100644 src/frontend/mod.rs create mode 100644 src/frontend/rest/mod.rs create mode 100644 src/frontend/rest/server.rs create mode 100644 src/frontend/rest/services/attributes.rs create mode 100644 src/frontend/rest/services/config.rs create mode 100644 src/frontend/rest/services/default_path.rs create mode 100644 src/frontend/rest/services/exit.rs create mode 100644 src/frontend/rest/services/install.rs create mode 100644 src/frontend/rest/services/installation_status.rs create mode 100644 src/frontend/rest/services/mod.rs create mode 100644 src/frontend/rest/services/packages.rs create mode 100644 src/frontend/rest/services/static_files.rs create mode 100644 src/frontend/rest/services/uninstall.rs create mode 100644 src/frontend/rest/services/update_updater.rs create mode 100644 src/frontend/ui/mod.rs delete mode 100644 src/rest.rs 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