use crate::{ config::*, results::{Endpoint, StatusGroup}, State, }; use chrono::Utc; use reqwest::{Client, RedirectPolicy, Url, UrlError}; use ring::{ digest::{digest, SHA256}, test::from_hex, }; use std::time::Duration; pub fn update_state(state: State) { let new_statuses = { Some(update_status(&state.read().unwrap().config)) }; let mut state = state.try_write().expect("Could not unlock"); state.update(new_statuses.unwrap()); } pub fn update_status(config: &Config) -> Vec { let mut results: Vec = Vec::with_capacity(config.websites.len()); for website_conf in &config.websites { let mut group = Vec::with_capacity(website_conf.endpoints.len()); for endpoint in &website_conf.endpoints { let mut client_builder = Client::builder().timeout(Some(Duration::from_secs(5))); if let Some(false) = endpoint.follow_redirects { client_builder = client_builder.redirect(RedirectPolicy::none()); } let client = client_builder.build().unwrap(); group.push(get_result(website_conf, &client, endpoint)); } results.push(StatusGroup { label: website_conf.label.clone(), endpoints: group, }); } results } fn get_result( website_conf: &WebsiteConfig, client: &Client, endpoint: &EndpointConfig, ) -> Endpoint { let (label, path, port, code, body) = get_endpoint_info(endpoint.clone()); let url = get_url(&website_conf.base, &path, port).expect("reading config"); let ping_start = Utc::now(); let ping_result = client.get(&url).send(); let rtt = Utc::now() - ping_start; let rtt_string = if rtt.num_seconds() > 0 { format!("{}s", rtt.num_milliseconds() as f64 / 1000.) } else { format!("{}ms", rtt.num_milliseconds()) }; match ping_result { Ok(mut res) => { let res_body = res.text().expect("could not get body of request"); let mut errors = vec![]; if res.status() != code { errors.push(format!( "Status code mismatch: {} != {}", res.status().as_u16(), code )); } if let Some(expected_hash) = &endpoint.body_hash { let expected = from_hex(expected_hash).unwrap(); let actual = digest(&SHA256, res_body.as_bytes()); if expected != actual.as_ref() { errors.push(String::from("Body hash mismatch.")); } } else if !body.is_empty() && res_body != body { errors.push(format!( "Body mismatch: {} != {}", res_body.len(), body.len() )); } if let Some(max_rtt) = endpoint.max_rtt { if rtt.num_milliseconds() > max_rtt { errors.push(format!( "RTT too long: {} > {}s", rtt_string, max_rtt as f64 / 1000. )); } } if !errors.is_empty() { Endpoint::warn(url, label, rtt_string, errors) } else { Endpoint::ok(url, label, rtt_string) } } Err(e) => { if let Some(true) = endpoint.should_err { Endpoint::ok(url, label, rtt_string) } else { Endpoint::error(url, label, vec![format!("{}", e)]) } } } } fn get_url(base: &Option, path: &str, port: Option) -> Result { let mut url = if let Some(base) = base { Url::parse(base)?.join(path)? } else { Url::parse(path)? }; if let Err(e) = url.set_port(port) { println!("{:?}", e); } Ok(url.into_string()) }