2019-05-01 20:59:55 +00:00
|
|
|
use crate::{
|
|
|
|
config::*,
|
|
|
|
handlers::{EndpointStatus, StatusGroup},
|
|
|
|
State,
|
|
|
|
};
|
2019-05-02 22:47:59 +00:00
|
|
|
use chrono::Utc;
|
2019-05-01 21:24:18 +00:00
|
|
|
use reqwest::{Client, RedirectPolicy, Url, UrlError};
|
2019-05-02 07:21:18 +00:00
|
|
|
use ring::{
|
|
|
|
digest::{digest, SHA256},
|
|
|
|
test::from_hex,
|
|
|
|
};
|
2019-05-01 22:02:38 +00:00
|
|
|
use std::time::Duration;
|
2019-05-01 18:13:46 +00:00
|
|
|
|
|
|
|
pub fn update_state(state: State) {
|
|
|
|
let new_statuses = { Some(update_status(&state.read().unwrap().config)) };
|
2019-05-03 04:12:09 +00:00
|
|
|
let mut state = state.try_write().expect("Could not unlock");
|
|
|
|
state.update(new_statuses.unwrap());
|
2019-05-01 18:13:46 +00:00
|
|
|
}
|
|
|
|
|
2019-05-01 20:59:55 +00:00
|
|
|
pub fn update_status(config: &Config) -> Vec<StatusGroup> {
|
|
|
|
let mut results: Vec<StatusGroup> = Vec::with_capacity(config.websites.len());
|
2019-05-01 18:13:46 +00:00
|
|
|
|
|
|
|
for website_conf in &config.websites {
|
2019-05-01 20:59:55 +00:00
|
|
|
let mut group = Vec::with_capacity(website_conf.endpoints.len());
|
2019-05-01 18:13:46 +00:00
|
|
|
for endpoint in &website_conf.endpoints {
|
2019-05-01 22:02:38 +00:00
|
|
|
let mut client_builder = Client::builder().timeout(Some(Duration::from_secs(5)));
|
2019-05-01 21:24:18 +00:00
|
|
|
|
|
|
|
if let Some(false) = endpoint.follow_redirects {
|
|
|
|
client_builder = client_builder.redirect(RedirectPolicy::none());
|
|
|
|
}
|
|
|
|
|
|
|
|
let client = client_builder.build().unwrap();
|
2019-05-01 20:59:55 +00:00
|
|
|
group.push(get_result(website_conf, &client, endpoint));
|
2019-05-01 18:13:46 +00:00
|
|
|
}
|
2019-05-01 20:59:55 +00:00
|
|
|
|
|
|
|
results.push(StatusGroup {
|
|
|
|
label: website_conf.label.clone(),
|
|
|
|
endpoints: group,
|
|
|
|
});
|
2019-05-01 18:13:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
results
|
|
|
|
}
|
|
|
|
|
2019-05-01 20:59:55 +00:00
|
|
|
fn get_result(
|
|
|
|
website_conf: &WebsiteConfig,
|
|
|
|
client: &Client,
|
|
|
|
endpoint: &EndpointConfig,
|
|
|
|
) -> EndpointStatus {
|
2019-05-01 18:13:46 +00:00
|
|
|
let (label, path, port, code, body) = get_endpoint_info(endpoint.clone());
|
|
|
|
let url = get_url(&website_conf.base, &path, port).expect("reading config");
|
2019-05-03 00:30:02 +00:00
|
|
|
|
2019-05-02 22:47:59 +00:00
|
|
|
let ping_start = Utc::now();
|
2019-05-01 18:13:46 +00:00
|
|
|
let ping_result = client.get(&url).send();
|
2019-05-03 00:30:02 +00:00
|
|
|
let rtt = Utc::now() - ping_start;
|
|
|
|
let rtt_string = if rtt.num_seconds() > 0 {
|
|
|
|
format!("{}s", rtt.num_milliseconds() as f64 / 1000.)
|
2019-05-02 22:47:59 +00:00
|
|
|
} else {
|
2019-05-03 00:30:02 +00:00
|
|
|
format!("{}ms", rtt.num_milliseconds())
|
2019-05-02 22:47:59 +00:00
|
|
|
};
|
2019-05-03 00:30:02 +00:00
|
|
|
|
2019-05-01 18:13:46 +00:00
|
|
|
match ping_result {
|
|
|
|
Ok(mut res) => {
|
|
|
|
let res_body = res.text().expect("could not get body of request");
|
|
|
|
let mut error = None;
|
|
|
|
|
2019-05-02 07:21:18 +00:00
|
|
|
if res.status() != code {
|
2019-05-03 00:30:02 +00:00
|
|
|
error = append_err_msg(
|
|
|
|
error,
|
|
|
|
format!(
|
|
|
|
"Status code mismatch: {} != {}.",
|
|
|
|
res.status().as_u16(),
|
|
|
|
code
|
|
|
|
),
|
|
|
|
);
|
2019-05-01 18:13:46 +00:00
|
|
|
}
|
|
|
|
|
2019-05-02 07:21:18 +00:00
|
|
|
if let Some(expected_hash) = &endpoint.body_hash {
|
|
|
|
let expected = from_hex(expected_hash).unwrap();
|
|
|
|
let actual = digest(&SHA256, String::from(res_body).as_bytes());
|
|
|
|
if &expected != &actual.as_ref() {
|
2019-05-03 00:30:02 +00:00
|
|
|
error = append_err_msg(error, String::from("Body hash mismatch."));
|
2019-05-02 07:21:18 +00:00
|
|
|
}
|
|
|
|
} else if !body.is_empty() && res_body != body {
|
2019-05-03 00:30:02 +00:00
|
|
|
error = append_err_msg(
|
|
|
|
error,
|
|
|
|
format!("Body mismatch: {} != {}.", res_body.len(), body.len()),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if rtt.num_seconds() >= 2 {
|
|
|
|
error = append_err_msg(error, format!("RTT too long: {}", rtt_string));
|
2019-05-01 18:13:46 +00:00
|
|
|
}
|
|
|
|
|
2019-05-02 02:47:11 +00:00
|
|
|
if error.is_some() {
|
2019-05-03 00:30:02 +00:00
|
|
|
EndpointStatus::warn(url, label, rtt_string, error)
|
2019-05-02 02:47:11 +00:00
|
|
|
} else {
|
2019-05-03 00:30:02 +00:00
|
|
|
EndpointStatus::ok(url, label, rtt_string)
|
2019-05-01 18:13:46 +00:00
|
|
|
}
|
|
|
|
}
|
2019-05-02 02:36:14 +00:00
|
|
|
Err(e) => {
|
|
|
|
if let Some(true) = endpoint.should_err {
|
2019-05-03 00:30:02 +00:00
|
|
|
EndpointStatus::ok(url, label, rtt_string)
|
2019-05-02 02:36:14 +00:00
|
|
|
} else {
|
2019-05-02 02:47:11 +00:00
|
|
|
EndpointStatus::error(url, label, Some(format!("{}", e)))
|
2019-05-02 02:36:14 +00:00
|
|
|
}
|
|
|
|
}
|
2019-05-01 18:13:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-01 21:16:54 +00:00
|
|
|
fn get_url(base: &Option<String>, path: &String, port: Option<u16>) -> Result<String, UrlError> {
|
|
|
|
let mut url = if let Some(base) = base {
|
|
|
|
Url::parse(base)?.join(path)?
|
|
|
|
} else {
|
|
|
|
Url::parse(path)?
|
|
|
|
};
|
2019-05-01 18:13:46 +00:00
|
|
|
if let Err(e) = url.set_port(port) {
|
|
|
|
println!("{:?}", e);
|
|
|
|
}
|
|
|
|
Ok(url.into_string())
|
|
|
|
}
|
2019-05-03 00:30:02 +00:00
|
|
|
|
|
|
|
fn append_err_msg(optional: Option<String>, to_append: String) -> Option<String> {
|
|
|
|
Some(match optional {
|
|
|
|
Some(e) => format!("{} {}", e, to_append),
|
|
|
|
None => to_append,
|
|
|
|
})
|
|
|
|
}
|