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 logging::LoggingErrors;
mod rest;
pub mod rest;
mod ui;
/// 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.
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.
use frontend::rest::services::default_future;
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 hyper::StatusCode;
use logging::LoggingErrors;
use http;
use config::Config;
use http::build_async_client;
use futures::stream::Stream;
use futures::Future as _;
pub fn handle(service: &WebService, _req: Request) -> Future {
let framework_url = {
service
@ -30,45 +31,54 @@ pub fn handle(service: &WebService, _req: Request) -> Future {
info!("Downloading configuration from {:?}...", framework_url);
default_future(
match http::download_text(&framework_url).map(|x| Config::from_toml_str(&x)) {
Ok(Ok(config)) => {
service.get_framework_write().config = Some(config.clone());
let framework = service.framework.clone();
// Hyper doesn't allow for clients to do sync network operations in a async future.
// 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.");
let file = service
.get_framework_read()
let file = framework
.get_config()
.log_expect("Config should be loaded by now")
.to_json_str()
.log_expect("Failed to render JSON representation of config");
Response::new()
Ok(Response::new()
.with_header(ContentLength(file.len() as u64))
.with_header(ContentType::json())
.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)
}
},
.with_body(file))
}),
)
}

View file

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