1pub mod error;
2use crate::error::ClientError;
3use reqwest::blocking::{Client, Response};
4use reqwest::header::AUTHORIZATION;
5use reqwest::header::{HeaderMap, HeaderValue};
6use scouter_settings::http::HttpConfig;
7use scouter_types::http::{JwtToken, RequestType, Routes};
8use serde_json::Value;
9use std::sync::Arc;
10use std::sync::RwLock;
11use tracing::{debug, error, instrument};
12
13const TIMEOUT_SECS: u64 = 60;
14
15pub fn build_http_client(settings: &HttpConfig) -> Result<Client, ClientError> {
17 let mut headers = HeaderMap::new();
18
19 headers.insert("Username", HeaderValue::from_str(&settings.username)?);
20 headers.insert("Password", HeaderValue::from_str(&settings.password)?);
21
22 let client_builder = Client::builder().timeout(std::time::Duration::from_secs(TIMEOUT_SECS));
23 let client = client_builder.default_headers(headers).build()?;
24 Ok(client)
25}
26
27#[derive(Debug, Clone)]
28pub struct HttpClient {
29 client: Client,
30 base_path: String,
31 pub auth_token: Arc<RwLock<String>>,
32}
33
34impl HttpClient {
35 pub fn new(config: HttpConfig) -> Result<Self, ClientError> {
36 let client = build_http_client(&config)?;
37 debug!("HttpClient created with base path: {}", config.server_uri);
38
39 let api_client = HttpClient {
40 client,
41 auth_token: Arc::new(RwLock::new(String::new())),
42 base_path: format!("{}/{}", config.server_uri, "scouter"),
43 };
44
45 api_client.refresh_token()?;
46
47 debug!(
48 "HTTPClient initialized with base path: {}",
49 api_client.base_path
50 );
51 Ok(api_client)
52 }
53
54 #[instrument(skip_all)]
55 pub fn refresh_token(&self) -> Result<(), ClientError> {
56 let url = format!("{}/{}", self.base_path, Routes::AuthLogin.as_str());
57 debug!("Getting JWT token from {}", url);
58
59 let response = self.client.get(url).send()?;
60
61 if response.status().is_client_error() {
63 error!("Unauthorized login request");
64 return Err(ClientError::Unauthorized);
65 }
66
67 let token = response.json::<JwtToken>().map_err(|e| {
69 error!("Failed to parse response as JSON for http: {}", e);
70 ClientError::ParseJwtTokenError(e.to_string())
71 })?;
72
73 if let Ok(mut token_guard) = self.auth_token.write() {
74 *token_guard = token.token;
75 } else {
76 error!("Failed to acquire write lock for token update");
77 return Err(ClientError::UpdateAuthTokenError);
78 }
79
80 Ok(())
81 }
82
83 pub fn update_token_from_response(&self, response: &Response) {
84 if let Some(new_token) = response
85 .headers()
86 .get(AUTHORIZATION)
87 .and_then(|h| h.to_str().ok())
88 .and_then(|h| h.strip_prefix("Bearer "))
89 {
90 match self.auth_token.write() {
91 Ok(mut token_guard) => {
92 *token_guard = new_token.to_string();
93 }
94 Err(e) => {
95 error!("Failed to acquire write lock for jwt token update: {}", e);
96 }
97 }
98 }
99 }
100
101 pub fn get_current_token(&self) -> String {
102 match self.auth_token.read() {
103 Ok(token_guard) => token_guard.clone(),
104 Err(e) => {
105 error!("Failed to acquire read lock for token: {}", e);
106 "".to_string()
107 }
108 }
109 }
110
111 fn _request(
112 &self,
113 route: Routes,
114 request_type: RequestType,
115 body_params: Option<Value>,
116 query_string: Option<String>,
117 headers: Option<HeaderMap>,
118 ) -> Result<Response, ClientError> {
119 let headers = headers.unwrap_or_default();
120
121 let url = format!("{}/{}", self.base_path, route.as_str());
122 let response = match request_type {
123 RequestType::Get => {
124 let url = if let Some(query_string) = query_string {
125 format!("{url}?{query_string}")
126 } else {
127 url
128 };
129
130 self.client
131 .get(url)
132 .headers(headers)
133 .bearer_auth(self.get_current_token())
134 .send()?
135 }
136 RequestType::Post => self
137 .client
138 .post(url)
139 .headers(headers)
140 .json(&body_params)
141 .bearer_auth(self.get_current_token())
142 .send()?,
143 RequestType::Put => self
144 .client
145 .put(url)
146 .headers(headers)
147 .json(&body_params)
148 .bearer_auth(self.get_current_token())
149 .send()?,
150 RequestType::Delete => {
151 let url = if let Some(query_string) = query_string {
152 format!("{url}?{query_string}")
153 } else {
154 url
155 };
156 self.client
157 .delete(url)
158 .headers(headers)
159 .bearer_auth(self.get_current_token())
160 .send()?
161 }
162 };
163
164 Ok(response)
165 }
166
167 pub fn request(
168 &self,
169 route: Routes,
170 request_type: RequestType,
171 body_params: Option<Value>,
172 query_params: Option<String>,
173 headers: Option<HeaderMap>,
174 ) -> Result<Response, ClientError> {
175 let response = self._request(
176 route.clone(),
177 request_type,
178 body_params,
179 query_params,
180 headers,
181 )?;
182
183 self.update_token_from_response(&response);
185
186 Ok(response)
187 }
188}