refactor into using tera

master
Edward Shen 2019-04-30 22:49:22 -04:00
parent 38cad2e788
commit cb9e094582
Signed by: edward
GPG Key ID: F350507060ED6C90
6 changed files with 732 additions and 213 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
/target target
**/*.rs.bk **/*.rs.bk
*.ron *.ron
.vscode/

796
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@ edition = "2018"
reqwest = "0.9.15" reqwest = "0.9.15"
serde = { version = "1.0.90", features = ["derive"] } serde = { version = "1.0.90", features = ["derive"] }
ron = "0.5" ron = "0.5"
actix-web = "0.7" actix-web = "1.0.0-beta.2"
tokio = "0.1" tokio = "0.1"
actix = "0.7" tera = "0.11"
env_logger = "0.6.1"

View File

@ -1,13 +1,19 @@
extern crate actix;
extern crate actix_web; extern crate actix_web;
extern crate env_logger;
extern crate reqwest; extern crate reqwest;
extern crate ron; extern crate ron;
extern crate serde; extern crate serde;
#[macro_use]
extern crate tera;
use actix::System; mod utils;
use self::utils::EpochTimestamp;
use actix_web::{ use actix_web::{
http::{Method, StatusCode}, error::ErrorInternalServerError,
server, App, HttpResponse, Json, Result as WebResult, State, middleware::Logger,
web::{resource, Data},
App, Error as WebError, HttpResponse, HttpServer, Result as WebResult,
}; };
use reqwest::{Client, Url, UrlError}; use reqwest::{Client, Url, UrlError};
use ron::de::from_str; use ron::de::from_str;
@ -16,10 +22,10 @@ use std::{
error::Error, error::Error,
fs::read_to_string, fs::read_to_string,
sync::{Arc, Mutex, MutexGuard}, sync::{Arc, Mutex, MutexGuard},
time::{Duration, Instant},
}; };
use tera::{Context, Tera};
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Serialize, Debug, Clone)]
struct EndpointConf { struct EndpointConf {
label: Option<String>, label: Option<String>,
endpoint: Option<String>, endpoint: Option<String>,
@ -28,21 +34,22 @@ struct EndpointConf {
body: Option<String>, body: Option<String>,
} }
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Serialize, Debug, Clone)]
struct WebsiteConf { struct WebsiteConf {
label: String, label: String,
base: String, base: String,
endpoints: Vec<EndpointConf>, endpoints: Vec<EndpointConf>,
} }
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Serialize, Debug, Clone)]
struct Config { struct Config {
refresh_time: u64, refresh_time: u64,
bind_address: String,
websites: Vec<WebsiteConf>, websites: Vec<WebsiteConf>,
} }
#[derive(Debug, Clone, Serialize)] #[derive(Clone, Serialize)]
struct Status { pub struct Status {
status: u8, status: u8,
location: String, location: String,
domain: String, domain: String,
@ -50,8 +57,9 @@ struct Status {
error: Option<String>, error: Option<String>,
} }
struct FetchResults { #[derive(Serialize)]
last_update: Instant, pub struct FetchResults {
last_update: EpochTimestamp,
refresh_time: u64, refresh_time: u64,
config: Config, config: Config,
statuses: Vec<Status>, statuses: Vec<Status>,
@ -59,19 +67,24 @@ struct FetchResults {
type StatusState = Arc<Mutex<FetchResults>>; type StatusState = Arc<Mutex<FetchResults>>;
fn index(state: State<StatusState>) -> HttpResponse { fn index(tmpl: Data<Tera>, state: Data<StatusState>) -> WebResult<HttpResponse, WebError> {
let state = update_state(state.lock().unwrap()); let state = update_state(state.lock().unwrap());
HttpResponse::with_body(StatusCode::OK, "") let mut ctx = Context::new();
ctx.insert("results", &*state);
let s = tmpl
.render("index.html", &tera::Context::new())
.map_err(|_| ErrorInternalServerError("Template error"))?;
Ok(HttpResponse::Ok().content_type("text/html").body(s))
} }
fn json_endpoint(state: State<StatusState>) -> WebResult<Json<Vec<Status>>> { fn json_endpoint(state: Data<StatusState>) -> HttpResponse {
let state = update_state(state.lock().unwrap()); let state = update_state(state.lock().unwrap());
Ok(Json(state.statuses.clone())) HttpResponse::Ok().json(&state.statuses)
} }
fn update_state(mut state: MutexGuard<FetchResults>) -> MutexGuard<FetchResults> { fn update_state(mut state: MutexGuard<FetchResults>) -> MutexGuard<FetchResults> {
if Instant::now().duration_since(state.last_update) > Duration::from_secs(state.refresh_time) { if EpochTimestamp::now() - state.last_update >= state.refresh_time {
state.last_update = Instant::now(); state.last_update = EpochTimestamp::now();
state.statuses = update_status(&state.config); state.statuses = update_status(&state.config);
} }
@ -80,23 +93,28 @@ fn update_state(mut state: MutexGuard<FetchResults>) -> MutexGuard<FetchResults>
fn main() -> Result<(), Box<Error>> { fn main() -> Result<(), Box<Error>> {
let config = from_str::<Config>(&read_to_string("./endstat_conf.ron")?)?; let config = from_str::<Config>(&read_to_string("./endstat_conf.ron")?)?;
let bind_addr = config.bind_address.clone();
std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
println!("running server"); HttpServer::new(move || {
server::new(move || {
let state = Arc::from(Mutex::from(FetchResults { let state = Arc::from(Mutex::from(FetchResults {
last_update: Instant::now(), last_update: EpochTimestamp::now(),
refresh_time: config.refresh_time, refresh_time: config.refresh_time.clone(),
config: config.clone(), config: config.clone(),
statuses: update_status(&config), statuses: update_status(&config),
})); }));
let tera = compile_templates!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*"));
App::with_state(state) App::new()
.resource("/", |r| r.get().with(index)) .data(state)
.resource("/api", |r| r.get().with(json_endpoint)) .data(tera)
.wrap(Logger::default())
.service(resource("/").to(index))
.service(resource("/api").to(json_endpoint))
}) })
.bind("0.0.0.0:8080")? .bind(&bind_addr)?
.run(); .run()?;
Ok(()) Ok(())
} }

49
src/utils.rs Normal file
View File

@ -0,0 +1,49 @@
use serde::{Serialize, Serializer};
use std::{
fmt::{Display, Formatter, Result as FmtResult},
ops::Sub,
time::{SystemTime, UNIX_EPOCH},
};
#[derive(PartialEq, PartialOrd, Copy, Clone)]
pub struct EpochTimestamp(SystemTime);
impl EpochTimestamp {
pub fn now() -> Self {
EpochTimestamp(SystemTime::now())
}
}
impl Sub for EpochTimestamp {
type Output = u64;
fn sub(self, other: EpochTimestamp) -> u64 {
self.0.duration_since(other.0).unwrap_or_default().as_secs()
}
}
impl Display for EpochTimestamp {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(
f,
"{}",
self.0
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
)
}
}
impl Serialize for EpochTimestamp {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_u64(
self.0
.duration_since(SystemTime::from(UNIX_EPOCH))
.unwrap_or_default()
.as_secs(),
)
}
}

18
templates/index.html Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Actix web</title>
</head>
<body>
<h1>Welcome!</h1>
<p>
<h3>What is your name?</h3>
<form>
<input type="text" name="name" /><br/>
<p><input type="submit"></p>
</form>
</p>
</body>
</html>