Update packages, use async client for downloading config

While this has a hell of a lot more boilerplate, this is quite
a bit cleaner.
This commit is contained in:
James 2019-06-23 21:24:13 +10:00
parent a447ef25b6
commit 4d50a0f8f8
5 changed files with 542 additions and 477 deletions

918
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ use std::sync::{Arc, RwLock};
use installer::InstallerFramework; use installer::InstallerFramework;
use logging::LoggingErrors; use logging::LoggingErrors;
mod rest; pub mod rest;
mod ui; mod ui;
/// Launches the main web server + UI. Returns when the framework has been consumed + web UI closed. /// Launches the main web server + UI. Returns when the framework has been consumed + web UI closed.

View File

@ -3,4 +3,4 @@
//! Contains the main web server used within the application. //! Contains the main web server used within the application.
pub mod server; pub mod server;
mod services; pub mod services;

View File

@ -4,21 +4,22 @@
//! //!
//! This endpoint should be usable directly from a <script> tag during loading. //! This endpoint should be usable directly from a <script> tag during loading.
use frontend::rest::services::default_future;
use frontend::rest::services::Future; use frontend::rest::services::Future;
use frontend::rest::services::Request; use frontend::rest::services::Request;
use frontend::rest::services::Response; use frontend::rest::services::Response;
use frontend::rest::services::WebService; use frontend::rest::services::WebService;
use hyper::header::{ContentLength, ContentType}; use hyper::header::{ContentLength, ContentType};
use hyper::StatusCode;
use logging::LoggingErrors; use logging::LoggingErrors;
use http;
use config::Config; use config::Config;
use http::build_async_client;
use futures::stream::Stream;
use futures::Future as _;
pub fn handle(service: &WebService, _req: Request) -> Future { pub fn handle(service: &WebService, _req: Request) -> Future {
let framework_url = { let framework_url = {
service service
@ -30,45 +31,54 @@ pub fn handle(service: &WebService, _req: Request) -> Future {
info!("Downloading configuration from {:?}...", framework_url); info!("Downloading configuration from {:?}...", framework_url);
default_future( let framework = service.framework.clone();
match http::download_text(&framework_url).map(|x| Config::from_toml_str(&x)) {
Ok(Ok(config)) => { // Hyper doesn't allow for clients to do sync network operations in a async future.
service.get_framework_write().config = Some(config.clone()); // This smallish pipeline joins the two together.
Box::new(
build_async_client()
.log_expect("Failed to build async client")
.get(&framework_url)
.send()
.map_err(|x| {
error!("HTTP error while downloading configuration file: {:?}", x);
hyper::Error::Incomplete
})
.and_then(|x| {
x.into_body().concat2().map_err(|x| {
error!("HTTP error while parsing configuration file: {:?}", x);
hyper::Error::Incomplete
})
})
.and_then(move |x| {
let x = String::from_utf8(x.to_vec()).map_err(|x| {
error!("UTF-8 error while parsing configuration file: {:?}", x);
hyper::Error::Incomplete
})?;
let config = Config::from_toml_str(&x).map_err(|x| {
error!("Serde error while parsing configuration file: {:?}", x);
hyper::Error::Incomplete
})?;
let mut framework = framework
.write()
.log_expect("Failed to get write lock for framework");
framework.config = Some(config);
info!("Configuration file downloaded successfully."); info!("Configuration file downloaded successfully.");
let file = service let file = framework
.get_framework_read()
.get_config() .get_config()
.log_expect("Config should be loaded by now") .log_expect("Config should be loaded by now")
.to_json_str() .to_json_str()
.log_expect("Failed to render JSON representation of config"); .log_expect("Failed to render JSON representation of config");
Response::new() Ok(Response::new()
.with_header(ContentLength(file.len() as u64)) .with_header(ContentLength(file.len() as u64))
.with_header(ContentType::json()) .with_header(ContentType::json())
.with_body(file) .with_body(file))
} }),
Ok(Err(v)) => {
error!("Bad configuration file: {:?}", v);
Response::new()
.with_status(StatusCode::ServiceUnavailable)
.with_header(ContentType::plaintext())
.with_body("Bad HTTP response")
}
Err(v) => {
error!(
"General connectivity error while downloading config: {:?}",
v
);
Response::new()
.with_status(StatusCode::ServiceUnavailable)
.with_header(ContentLength(v.len() as u64))
.with_header(ContentType::plaintext())
.with_body(v)
}
},
) )
} }

View File

@ -7,6 +7,7 @@ use reqwest::header::CONTENT_LENGTH;
use std::io::Read; use std::io::Read;
use std::time::Duration; use std::time::Duration;
use reqwest::async::Client as AsyncClient;
use reqwest::Client; use reqwest::Client;
/// Asserts that a URL is valid HTTPS, else returns an error. /// Asserts that a URL is valid HTTPS, else returns an error.
@ -26,18 +27,12 @@ pub fn build_client() -> Result<Client, String> {
.map_err(|x| format!("Unable to build client: {:?}", x)) .map_err(|x| format!("Unable to build client: {:?}", x))
} }
/// Downloads a text file from the specified URL. /// Builds a customised async HTTP client.
pub fn download_text(url: &str) -> Result<String, String> { pub fn build_async_client() -> Result<AsyncClient, String> {
assert_ssl(url)?; AsyncClient::builder()
.timeout(Duration::from_secs(8))
let mut client = build_client()? .build()
.get(url) .map_err(|x| format!("Unable to build client: {:?}", x))
.send()
.map_err(|x| format!("Failed to GET resource: {:?}", x))?;
client
.text()
.map_err(|v| format!("Failed to get text from resource: {:?}", v))
} }
/// Streams a file from a HTTP server. /// Streams a file from a HTTP server.