volcengine_rust_sdk/volcengine/request/
send.rs

1use crate::volcengine::error::error;
2use crate::volcengine::request::operation_config;
3use crate::volcengine::request::request;
4use crate::volcengine::request::sign;
5use crate::volcengine::request::sign::SignRequest;
6use chrono::Utc;
7use reqwest::{Client, RequestBuilder};
8use std::collections::HashMap;
9use std::future::Future;
10
11/// Trait for handling the creation, signing, and sending of API requests.
12/// Provides methods for building request headers, signing them, and sending requests.
13pub trait SendRequest {
14    /// Sets the request for the struct implementing this trait.
15    ///
16    /// This method takes a reference to a `request::Request` and returns an instance
17    /// of the implementing type with the request set.
18    ///
19    /// # Arguments
20    /// - `request`: A reference to a `request::Request` that will be set.
21    ///
22    /// # Returns
23    /// Returns an instance of the implementing struct with the `request` set.
24    fn set_request(request: &request::Request) -> Self;
25
26    /// Sends the request to the server and returns the response.
27    ///
28    /// This method is responsible for sending the request to the server asynchronously
29    /// and returning the server's response. It handles the serialization of the request,
30    /// making the API call, and processing the response.
31    ///
32    /// # Arguments
33    /// - `request`: A reference to a type implementing `ApiRequest`, which defines
34    ///   the structure of the request.
35    ///
36    /// # Returns
37    /// A future that resolves to a result containing the server's response or an error.
38    fn send<T: request::ApiRequest>(
39        &self,
40        request: &T,
41    ) -> impl Future<Output = Result<reqwest::Response, error::Error>>;
42
43    /// Builds the request headers, including all necessary metadata.
44    ///
45    /// This method is responsible for building the headers for the HTTP request. The headers
46    /// are added to the request builder, which will later be used to make the API call.
47    ///
48    /// # Arguments
49    /// - `now_date`: The current date in the required format, typically used for signing headers.
50    /// - `request_builder`: A `RequestBuilder` instance that will be populated with the headers.
51    /// - `request`: A reference to a type implementing `ApiRequest` to extract specific header data.
52    ///
53    /// # Returns
54    /// Returns a `Result` containing either the populated `RequestBuilder` with headers or an error.
55    fn build_request_headers<T: request::ApiRequest>(
56        &self,
57        now_date: &str,
58        request_builder: RequestBuilder,
59        request: &T,
60    ) -> Result<RequestBuilder, error::Error>;
61
62    /// Builds the request headers and adds the authorization header.
63    ///
64    /// This method extends `build_request_headers` by adding an authorization header to
65    /// the request. The authorization header is typically generated using an HMAC or similar
66    /// signing method to ensure secure communication.
67    ///
68    /// # Arguments
69    /// - `now_date`: The current UTC date in a specific format, used for signing the authorization.
70    /// - `request`: A reference to a type implementing `ApiRequest`, which provides data for the request.
71    /// - `request_builder`: The request builder to which the authorization header will be added.
72    ///
73    /// # Returns
74    /// Returns a `Result` containing the modified `RequestBuilder` with the authorization header
75    /// or an error.
76    fn build_request_headers_authorization<T: request::ApiRequest>(
77        &self,
78        now_date: &str,
79        request: &T,
80        request_builder: RequestBuilder,
81    ) -> Result<RequestBuilder, error::Error>;
82
83    /// Builds the request object itself.
84    ///
85    /// This method is responsible for constructing the base HTTP request, including
86    /// the URL, HTTP method, and other essential components required to perform the request.
87    ///
88    /// # Arguments
89    /// - `request`: A reference to a type implementing `ApiRequest` to obtain the necessary data
90    ///   for the request, such as the URL and HTTP method.
91    ///
92    /// # Returns
93    /// Returns a `Result` containing the constructed `RequestBuilder` or an error.
94    fn build_request<T: request::ApiRequest>(
95        &self,
96        request: &T,
97    ) -> Result<RequestBuilder, error::Error>;
98
99    /// Converts the request parameters into a `HashMap`.
100    ///
101    /// This method formats the parameters for the request into a `HashMap`, where the keys
102    /// are the names of the parameters and the values are their respective values.
103    ///
104    /// # Arguments
105    /// - `request`: A reference to a type implementing `ApiRequest`, which contains the parameters
106    ///   to be formatted.
107    ///
108    /// # Returns
109    /// A `HashMap<String, String>` containing the formatted request parameters.
110    fn format_request_hashmap<T: request::ApiRequest>(
111        &self,
112        request: &T,
113    ) -> HashMap<String, String>;
114
115    /// Retrieves the current UTC date formatted as a string.
116    ///
117    /// This method generates the current UTC date in a specific format, which is commonly used
118    /// for API signature generation or as part of headers for request authentication.
119    ///
120    /// # Returns
121    /// A string representing the current UTC date in the required format (e.g., `20250205T120000Z`).
122    fn get_x_date(&self) -> String;
123}
124
125/// Represents a `Send` struct used for handling an API request.
126///
127/// This struct is designed to hold an API request (`request::Request`) and provide methods
128/// for processing and sending the request to a server. The struct can be cloned and debugged
129/// for testing or inspection purposes.
130#[derive(Debug, Clone)]
131pub struct Send {
132    /// The request instance to be sent.
133    ///
134    /// This field holds the request object of type `request::Request`, which contains the data
135    /// that will be sent to the API endpoint. It includes parameters such as the URL, method,
136    /// and body of the request.
137    ///
138    /// # Fields
139    /// - `request`: The `request::Request` object that will be sent to the server.
140    request: request::Request,
141}
142
143/**
144 * @description: Implementation of SendRequest trait for Send struct.
145 * @author: Jerry.Yang
146 * @date: 2024-11-08 10:46:32
147 * @return {*}
148 */
149impl SendRequest for Send {
150    /// Sets the request for the Send instance.
151    ///
152    /// # Arguments
153    /// - `request`: The request object to be set.
154    ///
155    /// # Returns
156    /// Returns an instance of `Send`.
157    fn set_request(request: &request::Request) -> Self {
158        Send {
159            request: request.clone(),
160        }
161    }
162
163    /// Sends the HTTP request.
164    ///
165    /// # Arguments
166    /// - `request`: The request object to be sent.
167    ///
168    /// # Returns
169    /// Returns a `Result` containing either the `reqwest::Response` or an error.
170    async fn send<T: request::ApiRequest>(
171        &self,
172        request: &T,
173    ) -> Result<reqwest::Response, error::Error> {
174        // Get the current date and time in UTC.
175        let now_date_string = self.get_x_date();
176        let now_date = now_date_string.as_str();
177
178        // Build the request.
179        let request_builder = self.build_request(request)?;
180
181        // Build the request headers.
182        let request_builder = self.build_request_headers(&now_date, request_builder, request)?;
183
184        // Add the authorization header.
185        let request_builder =
186            self.build_request_headers_authorization(&now_date, request, request_builder)?;
187
188        // Send the request and handle the response.
189        let response = request_builder
190            .send()
191            .await
192            .map_err(|e| error::Error::ErrRequest(e))?;
193
194        // Return the response.
195        Ok(response)
196    }
197
198    /// Builds the request headers.
199    ///
200    /// # Arguments
201    /// - `now_date`: The current date and time as a string.
202    /// - `request_builder`: The request builder.
203    /// - `request`: The request object.
204    ///
205    /// # Returns
206    /// Returns the request builder with headers or an error.
207    fn build_request_headers<T: request::ApiRequest>(
208        &self,
209        now_date: &str,
210        request_builder: RequestBuilder,
211        request: &T,
212    ) -> Result<RequestBuilder, error::Error> {
213        // Create a signing object.
214        let request_sign = sign::Sign::default();
215
216        // Clone the request builder.
217        let request_builder_clone = request_builder
218            .try_clone()
219            .ok_or_else(|| error::Error::ErrRequestBuilderIsNone)?;
220
221        // Add basic headers.
222        let request_builder_with_headers = request_builder_clone.header("X-Date", now_date);
223
224        // Get the request method and set headers based on the method.
225        let reqwest_request = request_builder
226            .build()
227            .map_err(|e| error::Error::ErrRequest(e))?;
228
229        // Get the HTTP method.
230        let method = reqwest_request.method().as_str();
231
232        // If the method is POST, add additional headers.
233        if method == "POST" {
234            // Hash the request body to create a digest.
235            let payload_hash = hex::encode(request_sign.hash_sha256(&request.to_body()));
236
237            // Add the `X-Content-Sha256` header.
238            let request_builder_with_headers =
239                request_builder_with_headers.header("X-Content-Sha256", payload_hash);
240            return Ok(request_builder_with_headers);
241        }
242
243        // Return the request builder with headers.
244        Ok(request_builder_with_headers)
245    }
246
247    /// Builds the authorization header.
248    ///
249    /// # Arguments
250    /// - `now_date`: The current date and time as a string.
251    /// - `request`: The request object.
252    /// - `request_builder`: The request builder.
253    ///
254    /// # Returns
255    /// Returns the request builder with the authorization header.
256    fn build_request_headers_authorization<T: request::ApiRequest>(
257        &self,
258        now_date: &str,
259        request: &T,
260        request_builder: RequestBuilder,
261    ) -> Result<RequestBuilder, error::Error> {
262        // Clone the request builder.
263        let request_builder_clone = request_builder
264            .try_clone()
265            .ok_or_else(|| error::Error::ErrRequestBuilderIsNone)?;
266
267        // Build the request and get the signing headers.
268        let reqwest_request = request_builder_clone
269            .build()
270            .map_err(|e| error::Error::ErrRequest(e))?;
271        let request_sign = sign::Sign::default();
272        let sign_headers = request_sign.get_sign_header_keys(&reqwest_request);
273
274        // Create a vector of lowercase signing headers.
275        let mut authorization_sign_headers: Vec<String> = Vec::new();
276        for sign_header in sign_headers {
277            authorization_sign_headers.push(sign_header.to_lowercase());
278        }
279
280        // Join the signing headers into a single string.
281        let authorization_sign_header_str = authorization_sign_headers.join(";");
282
283        // Generate the signature.
284        let signature =
285            request_sign.build_signature(now_date, &self.request, request, &request_builder)?;
286
287        // Build the `Authorization` header.
288        let short_date = &now_date[..8];
289        let authorization = format!(
290            r#"HMAC-SHA256 Credential={AccessKey}/{ShortDate}/{Region}/{Service}/request, SignedHeaders={SignedHeaders}, Signature={Signature}"#,
291            AccessKey = self.request.config.config.credentials.access_key_id,
292            ShortDate = short_date,
293            Region = self.request.client_info.signing_region,
294            Service = self.request.client_info.service_name.as_str(),
295            SignedHeaders = authorization_sign_header_str,
296            Signature = signature,
297        );
298
299        // Add the authorization header to the request.
300        let request_builder_with_headers = request_builder.header("Authorization", authorization);
301
302        // Return the request builder with the authorization header.
303        Ok(request_builder_with_headers)
304    }
305
306    /// Builds the request.
307    ///
308    /// # Arguments
309    /// - `request`: The request object.
310    ///
311    /// # Returns
312    /// Returns the request builder or an error.
313    fn build_request<T: request::ApiRequest>(
314        &self,
315        request: &T,
316    ) -> Result<RequestBuilder, error::Error> {
317        let client = Client::new();
318
319        // Format the request parameters into a query string.
320        let query_string = self
321            .format_request_hashmap(request)
322            .iter()
323            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
324            .collect::<Vec<String>>()
325            .join("&");
326
327        // Set the request address.
328        let request_addr = format!(
329            "{}{}?{}",
330            self.request.config.endpoint,
331            self.request.operation.clone().http_path.to_string(),
332            query_string
333        );
334
335        // Build the request based on the HTTP method.
336        let request_builder = match self.request.operation.http_method {
337            operation_config::operation_http_method::OperationHttpMethod::GET => {
338                client.get(&request_addr)
339            }
340            operation_config::operation_http_method::OperationHttpMethod::POST => {
341                let body = request.to_body();
342                client.post(&request_addr).body(body)
343            }
344        };
345
346        // Return the request builder.
347        Ok(request_builder)
348    }
349
350    /// Formats the request into a HashMap.
351    ///
352    /// # Arguments
353    /// - `request`: The request object.
354    ///
355    /// # Returns
356    /// Returns the formatted HashMap.
357    fn format_request_hashmap<T: request::ApiRequest>(
358        &self,
359        request: &T,
360    ) -> HashMap<String, String> {
361        // Define the request HashMap.
362        let mut request_hashmap = request.to_hashmap();
363
364        // Set the Action parameter.
365        request_hashmap.insert(
366            String::from("Action"),
367            self.request.operation.name.to_string(),
368        );
369
370        // Set the Version parameter.
371        request_hashmap.insert(
372            String::from("Version"),
373            self.request.client_info.api_version.to_string(),
374        );
375
376        // Return the formatted HashMap.
377        request_hashmap
378    }
379
380    /// Gets the current date and time in UTC as a formatted string.
381    ///
382    /// # Returns
383    /// Returns the formatted date and time as a String.
384    fn get_x_date(&self) -> String {
385        Utc::now().format("%Y%m%dT%H%M%SZ").to_string()
386    }
387}