scouter_client/http/
base.rs

1// we redifine HTTPClient here because the scouterClient needs a blocking httpclient, unlike the producer enum
2
3use 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
16/// Create a new HTTP client that can be shared across different clients
17pub 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        // check if unauthorized
63        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        // Check and update token if a new one was provided
180        self.update_token_from_response(&response);
181
182        Ok(response)
183    }
184}