Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(http): Make checks using request configs #161

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 69 additions & 16 deletions src/checker/http_checker.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use chrono::{TimeDelta, Utc};
use reqwest::header::HeaderMap;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use reqwest::{Client, ClientBuilder, Response};
use sentry::protocol::{SpanId, TraceId};
use std::error::Error;
Expand All @@ -9,10 +9,7 @@ use uuid::Uuid;
use crate::config_store::Tick;
use crate::types::{
check_config::CheckConfig,
result::{
CheckResult, CheckStatus, CheckStatusReason, CheckStatusReasonType, RequestInfo,
RequestType,
},
result::{CheckResult, CheckStatus, CheckStatusReason, CheckStatusReasonType, RequestInfo},
};

use super::Checker;
Expand All @@ -33,20 +30,34 @@ async fn do_request(
client: &Client,
check_config: &CheckConfig,
sentry_trace: &str,
) -> (RequestType, Result<Response, reqwest::Error>) {
) -> Result<Response, reqwest::Error> {
let timeout = check_config
.timeout
.to_std()
.expect("Timeout duration could not be converted to std::time::Duration");

let result = client
.get(check_config.url.as_str())
let url = check_config.url.as_str();

let headers: HeaderMap = check_config
.request_headers
.clone()
.into_iter()
.filter_map(|(key, value)| {
// Try to convert key and value to HeaderName and HeaderValue
let header_name = HeaderName::try_from(key).ok()?;
let header_value = HeaderValue::from_str(&value).ok()?;
Some((header_name, header_value))
})
.collect();

client
.request(check_config.request_method.into(), url)
.timeout(timeout)
.headers(headers)
.header("sentry-trace", sentry_trace.to_owned())
.body(check_config.request_body.to_owned())
.send()
.await;

(RequestType::Get, result)
.await
}

/// Check if the request error is a DNS error.
Expand Down Expand Up @@ -96,7 +107,7 @@ impl Checker for HttpChecker {
let trace_header = format!("{}-{}-{}", trace_id, span_id, '0');

let start = Instant::now();
let (request_type, response) = do_request(&self.client, config, &trace_header).await;
let response = do_request(&self.client, config, &trace_header).await;
let duration = Some(TimeDelta::from_std(start.elapsed()).unwrap());

let status = if response.as_ref().is_ok_and(|r| r.status().is_success()) {
Expand All @@ -112,7 +123,7 @@ impl Checker for HttpChecker {

let request_info = Some(RequestInfo {
http_status_code,
request_type,
request_type: config.request_method,
});

let status_reason = match response {
Expand Down Expand Up @@ -158,10 +169,13 @@ impl Checker for HttpChecker {

#[cfg(test)]
mod tests {
use std::collections::HashMap;

use crate::checker::Checker;
use crate::config_store::Tick;
use crate::types::check_config::CheckConfig;
use crate::types::result::{CheckStatus, CheckStatusReasonType, RequestType};
use crate::types::result::{CheckStatus, CheckStatusReasonType};
use crate::types::shared::RequestMethod;

use super::{HttpChecker, UPTIME_USER_AGENT};
use chrono::{TimeDelta, Utc};
Expand All @@ -173,7 +187,7 @@ mod tests {
}

#[tokio::test]
async fn test_simple_get() {
async fn test_default_get() {
let server = MockServer::start();
let checker = HttpChecker::new();

Expand All @@ -196,7 +210,46 @@ mod tests {
assert_eq!(result.status, CheckStatus::Success);
assert_eq!(
result.request_info.as_ref().map(|i| i.request_type),
Some(RequestType::Get)
Some(RequestMethod::Get)
);

get_mock.assert();
}

#[tokio::test]
async fn test_configured_post() {
let server = MockServer::start();
let checker = HttpChecker::new();

let get_mock = server.mock(|when, then| {
when.method(Method::POST)
.path("/no-head")
.header_exists("sentry-trace")
.body("{\"key\":\"value\"}")
.header("User-Agent", UPTIME_USER_AGENT.to_string())
.header("Authorization", "Bearer my-token".to_string())
.header("X-My-Custom-Header", "value".to_string());
then.status(200);
});

let config = CheckConfig {
url: server.url("/no-head").to_string(),
request_method: RequestMethod::Post,
request_headers: HashMap::from([
("Authorization".to_string(), "Bearer my-token".to_string()),
("X-My-Custom-Header".to_string(), "value".to_string()),
]),
request_body: "{\"key\":\"value\"}".to_string(),
..Default::default()
};

let tick = make_tick();
let result = checker.check_url(&config, &tick).await;

assert_eq!(result.status, CheckStatus::Success);
assert_eq!(
result.request_info.as_ref().map(|i| i.request_type),
Some(RequestMethod::Post)
);

get_mock.assert();
Expand Down
10 changes: 6 additions & 4 deletions src/producer/kafka_producer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ mod tests {
use super::KafkaResultsProducer;
use crate::{
producer::{ExtractCodeError, ResultsProducer},
types::result::{
CheckResult, CheckStatus, CheckStatusReason, CheckStatusReasonType, RequestInfo,
RequestType,
types::{
result::{
CheckResult, CheckStatus, CheckStatusReason, CheckStatusReasonType, RequestInfo,
},
shared::RequestMethod,
},
};
use chrono::{TimeDelta, Utc};
Expand All @@ -78,7 +80,7 @@ mod tests {
actual_check_time: Utc::now(),
duration: Some(TimeDelta::seconds(1)),
request_info: Some(RequestInfo {
request_type: RequestType::Head,
request_type: RequestMethod::Get,
http_status_code: Some(200),
}),
};
Expand Down
1 change: 1 addition & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod check_config;
pub mod result;
pub mod shared;
21 changes: 2 additions & 19 deletions src/types/check_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use std::{
};
use uuid::Uuid;

use super::shared::RequestMethod;

const ONE_MINUTE: isize = 60;

/// Valid intervals between the checks in seconds.
Expand All @@ -25,25 +27,6 @@ pub enum CheckInterval {
/// The largest check interval
pub const MAX_CHECK_INTERVAL_SECS: usize = CheckInterval::SixtyMinutes as usize;

/// Request methods available for the check configuration.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum RequestMethod {
Get,
Post,
Head,
Put,
Delete,
Patch,
Options,
}

impl Default for RequestMethod {
fn default() -> Self {
Self::Get
}
}

/// The CheckConfig represents a configuration for a single check.
#[serde_as]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
Expand Down
12 changes: 3 additions & 9 deletions src/types/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use serde_with::chrono;
use serde_with::serde_as;
use uuid::Uuid;

use super::shared::RequestMethod;

fn uuid_simple<S>(uuid: &Uuid, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
Expand All @@ -30,14 +32,6 @@ pub enum CheckStatusReasonType {
Failure,
}

/// The type of HTTP request used for the check
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RequestType {
Head,
Get,
}

/// Captures the reason for a check's given status
#[derive(Debug, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
Expand All @@ -54,7 +48,7 @@ pub struct CheckStatusReason {
#[serde(rename_all = "snake_case")]
pub struct RequestInfo {
/// The type of HTTP method used for the check
pub request_type: RequestType,
pub request_type: RequestMethod,

/// The status code of the response. May be empty when the request did not receive a response
/// whatsoever.
Expand Down
34 changes: 34 additions & 0 deletions src/types/shared.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use serde::{Deserialize, Serialize};

/// Common requets methods.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum RequestMethod {
Get,
Post,
Head,
Put,
Delete,
Patch,
Options,
}

impl Default for RequestMethod {
fn default() -> Self {
Self::Get
}
}

impl From<RequestMethod> for reqwest::Method {
fn from(value: RequestMethod) -> Self {
match value {
RequestMethod::Get => reqwest::Method::GET,
RequestMethod::Post => reqwest::Method::POST,
RequestMethod::Head => reqwest::Method::HEAD,
RequestMethod::Put => reqwest::Method::PUT,
RequestMethod::Delete => reqwest::Method::DELETE,
RequestMethod::Patch => reqwest::Method::PATCH,
RequestMethod::Options => reqwest::Method::OPTIONS,
}
}
}
Loading