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}