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
40 let api_client = HTTPClient {
41 client,
42 auth_token: Arc::new(RwLock::new(String::new())),
43 base_path: format!("{}/{}", config.server_uri, "scouter"),
44 };
45
46 api_client.refresh_token()?;
47
48 debug!(
49 "HTTPClient initialized with base path: {}",
50 api_client.base_path
51 );
52 Ok(api_client)
53 }
54
55 #[instrument(skip_all)]
56 fn refresh_token(&self) -> Result<(), ClientError> {
57 let url = format!("{}/{}", self.base_path, Routes::AuthLogin.as_str());
58 debug!("Getting JWT token from {}", url);
59
60 let response = self.client.get(url).send()?;
61
62 if response.status().is_client_error() {
64 return Err(ClientError::Unauthorized);
65 }
66
67 let token = response.json::<JwtToken>()?;
68
69 if let Ok(mut token_guard) = self.auth_token.write() {
70 *token_guard = token.token;
71 } else {
72 error!("Failed to acquire write lock for token update");
73 return Err(ClientError::UpdateAuthTokenError);
74 }
75
76 Ok(())
77 }
78
79 fn update_token_from_response(&self, response: &Response) {
80 if let Some(new_token) = response
81 .headers()
82 .get(AUTHORIZATION)
83 .and_then(|h| h.to_str().ok())
84 .and_then(|h| h.strip_prefix("Bearer "))
85 {
86 match self.auth_token.write() {
87 Ok(mut token_guard) => {
88 *token_guard = new_token.to_string();
89 }
90 Err(e) => {
91 error!("Failed to acquire write lock for jwt token update: {}", e);
92 }
93 }
94 }
95 }
96
97 fn get_current_token(&self) -> String {
98 match self.auth_token.read() {
99 Ok(token_guard) => token_guard.clone(),
100 Err(e) => {
101 error!("Failed to acquire read lock for token: {}", e);
102 "".to_string()
103 }
104 }
105 }
106
107 fn _request(
108 &self,
109 route: Routes,
110 request_type: RequestType,
111 body_params: Option<Value>,
112 query_string: Option<String>,
113 headers: Option<HeaderMap>,
114 ) -> Result<Response, ClientError> {
115 let headers = headers.unwrap_or_default();
116
117 let url = format!("{}/{}", self.base_path, route.as_str());
118 let response = match request_type {
119 RequestType::Get => {
120 let url = if let Some(query_string) = query_string {
121 format!("{url}?{query_string}")
122 } else {
123 url
124 };
125
126 self.client
127 .get(url)
128 .headers(headers)
129 .bearer_auth(self.get_current_token())
130 .send()?
131 }
132 RequestType::Post => self
133 .client
134 .post(url)
135 .headers(headers)
136 .json(&body_params)
137 .bearer_auth(self.get_current_token())
138 .send()?,
139 RequestType::Put => self
140 .client
141 .put(url)
142 .headers(headers)
143 .json(&body_params)
144 .bearer_auth(self.get_current_token())
145 .send()?,
146 RequestType::Delete => {
147 let url = if let Some(query_string) = query_string {
148 format!("{url}?{query_string}")
149 } else {
150 url
151 };
152 self.client
153 .delete(url)
154 .headers(headers)
155 .bearer_auth(self.get_current_token())
156 .send()?
157 }
158 };
159
160 Ok(response)
161 }
162
163 pub fn request(
164 &self,
165 route: Routes,
166 request_type: RequestType,
167 body_params: Option<Value>,
168 query_params: Option<String>,
169 headers: Option<HeaderMap>,
170 ) -> Result<Response, ClientError> {
171 let response = self._request(
172 route.clone(),
173 request_type,
174 body_params,
175 query_params,
176 headers,
177 )?;
178
179 self.update_token_from_response(&response);
181
182 Ok(response)
183 }
184}