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