use reqwest::blocking::{Client, Response};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::thread;
use std::time::Duration;
pub trait Reporter {
fn start(&self, uk: &str) -> Result<u64, Box<dyn Error>>;
fn finish(&self, uk: &str, msg: &str) -> Result<(), Box<dyn Error>>;
}
#[derive(Clone)]
pub struct HttpReporter {
client: Client,
host: String,
name: String,
}
impl HttpReporter {
pub fn new(host: String, name: String) -> Self {
let client = Client::builder()
.timeout(Duration::from_secs(10))
.danger_accept_invalid_certs(true)
.build()
.unwrap();
HttpReporter { client, host, name }
}
fn retry_request<F>(&self, request_fn: F) -> Result<Response, reqwest::Error>
where
F: Fn() -> reqwest::blocking::RequestBuilder,
{
let mut retries = 3;
let mut wait_time = Duration::from_secs(5);
while retries > 0 {
let response = request_fn().send();
match response {
Ok(resp) => {
if resp.status() == StatusCode::OK || resp.status().is_success() {
return Ok(resp);
}
if resp.status().is_server_error() {
thread::sleep(wait_time);
retries -= 1;
wait_time *= 2;
} else {
return Ok(resp);
}
}
Err(e) => {
if retries == 1 {
return Err(e);
}
thread::sleep(wait_time);
retries -= 1;
wait_time *= 2;
}
}
}
request_fn().send()
}
}
impl Reporter for HttpReporter {
fn start(&self, uk: &str) -> Result<u64, Box<dyn Error>> {
if self.host.is_empty() {
return Ok(0);
}
#[derive(Deserialize)]
struct RespData {
id: String,
code: Option<i64>,
message: Option<String>,
}
#[derive(Serialize)]
struct ReqBody<'a> {
action: &'a str,
uk_function_name: &'a str,
uk: &'a str,
}
let body = ReqBody {
action: "start",
uk_function_name: &self.name,
uk,
};
let response = self
.retry_request(|| {
self.client
.post(format!("{}/v1/dashboard/report", self.host))
.json(&body)
})?;
let status = response.status();
let resp_data: RespData = response.json()?;
if status != StatusCode::OK {
return Err(format!("resp status code: {}", status).into());
}
if resp_data.code.is_some() {
return Err(format!("resp code: {}, msg: {}", resp_data.code.unwrap(), resp_data.message.unwrap_or("".to_string())).into());
}
Ok(resp_data.id.parse()?)
}
fn finish(&self, uk: &str, msg: &str) -> Result<(), Box<dyn Error>> {
if self.host.is_empty() {
return Ok(());
}
#[derive(Serialize)]
struct ReqBody<'a> {
action: &'a str,
uk_function_name: &'a str,
uk: &'a str,
message: &'a str,
}
let body = ReqBody {
action: "finished",
uk_function_name: &self.name,
uk,
message: msg,
};
let response = self
.retry_request(|| {
self.client
.post(format!("{}/v1/dashboard/report", self.host))
.json(&body)
})?;
if response.status() != StatusCode::OK {
return Err(format!("resp status code: {}", response.status()).into());
}
Ok(())
}
}