yao_dev_common/http/
http_util.rs

1use crate::error::app_error::AppError;
2use crate::http::parser::{JsonParser, ResponseParser, StringParser};
3use reqwest::{Client, Error, Method, RequestBuilder, Response};
4use serde::de::DeserializeOwned;
5use serde::{Deserialize, Serialize};
6use std::any::TypeId;
7use std::collections::HashMap;
8use std::marker::PhantomData;
9use std::time::Duration;
10
11#[derive(Debug, Serialize, Deserialize, Clone)]
12pub struct Headers {
13    pub header: Option<HashMap<String, String>>,
14    pub auth: Option<Auth>,
15}
16#[derive(Debug, Serialize, Deserialize, Clone)]
17pub struct Auth {
18    pub username: String,
19    pub password: String,
20    pub token: String,
21}
22
23#[derive(Debug)]
24pub enum HttpMethod {
25    GET,
26    POST,
27    PUT,
28    DELETE,
29    HEAD,
30}
31
32// 主函数根据类型动态选择解析器
33async fn parse_response<T>(response: Response) -> Result<T, AppError>
34where
35    T: for<'a> Deserialize<'a> + std::fmt::Debug + 'static + From<String>,
36{
37    let body_str = response.text().await?;
38
39    // 通过类型匹配选择解析器
40    if TypeId::of::<T>() == TypeId::of::<String>() {
41        Ok(<StringParser as ResponseParser<T>>::parse(&body_str)?)
42    } else {
43        Ok(<JsonParser<T> as ResponseParser<T>>::parse(&body_str)?)
44    }
45}
46
47/// GET 请求
48///
49/// # params
50/// - `base_url`: request url
51/// - `params`: request params, &[(&str, &str)]
52/// - `headers`: request headers, Option<Headers>
53///
54/// # return
55/// - `Option<T>`: for<'a> Deserialize<'a>
56///
57pub async fn do_get<T, E>(
58    base_url: &str,
59    params: Option<HashMap<String, String>>,
60    headers: Option<Headers>,
61    show_debug: Option<bool>,
62) -> Result<T, E>
63where
64    T: for<'a> Deserialize<'a> + std::fmt::Debug + 'static + From<String>,
65    E: std::fmt::Debug
66        + std::fmt::Display
67        + From<AppError>
68        + From<reqwest::Error>
69        + From<serde_json::Error>,
70{
71    // build request client and get response
72    let response = request_build(base_url, Method::GET, params, "", headers, show_debug).await?;
73    if response.status().is_success() {
74        // let result = serde_json::from_str::<T>(&body_str)?;
75        let result: T = parse_response(response).await?;
76        tracing::info!("[do_get] http result: {:?}", result);
77        return Ok(result);
78    }
79    Err(AppError::from(response.text().await?).into())
80}
81
82/// POST 请求 with params
83///
84/// # params
85/// - `base_url`: request url
86/// - `params`: request params, &[(&str, &str)]
87/// - `headers`: request headers, Option<Headers>
88///
89/// # return
90/// - `Option<T>`: for<'a> Deserialize<'a>
91///
92pub async fn do_post_with_params<T, E>(
93    base_url: &str,
94    params: Option<HashMap<String, String>>,
95    headers: Option<Headers>,
96    show_debug: Option<bool>,
97) -> Result<T, E>
98where
99    T: for<'a> Deserialize<'a> + std::fmt::Debug + 'static + From<String>,
100    E: std::fmt::Debug
101        + std::fmt::Display
102        + From<AppError>
103        + From<reqwest::Error>
104        + From<serde_json::Error>,
105{
106    // build request client and get response
107    let response = request_build(base_url, Method::POST, params, "", headers, show_debug).await?;
108    // parse response to json
109    let result: T = parse_response(response).await?;
110    //
111    tracing::info!("[do_post_with_params] http result: {:?}", result);
112    Ok(result)
113}
114
115/// POST 请求 with body
116///
117/// # params
118/// - `base_url`: request url
119/// - `params`: request params, &[(&str, &str)]
120/// - `headers`: request headers, Option<Headers>
121/// - `body`: request body, &str
122///
123/// # return
124/// - `Option<T>`: for<'a> Deserialize<'a>
125///
126pub async fn do_post_with_body<T, E>(
127    base_url: &str,
128    params: Option<HashMap<String, String>>,
129    body: &str,
130    headers: Option<Headers>,
131    show_debug: Option<bool>,
132) -> Result<T, E>
133where
134    T: for<'a> Deserialize<'a> + std::fmt::Debug + 'static + From<String>,
135    E: std::fmt::Debug
136        + std::fmt::Display
137        + From<AppError>
138        + From<reqwest::Error>
139        + From<serde_json::Error>,
140{
141    // build request client and get response
142    let response = request_build(base_url, Method::POST, params, body, headers, show_debug).await?;
143    // parse response to json
144    let result: T = parse_response(response).await?;
145    //
146    tracing::info!("[do_post_with_body] http result: {:?}", result);
147    Ok(result)
148}
149
150pub async fn do_something<T, E>(
151    base_url: &str,
152    method: Method,
153    params: Option<HashMap<String, String>>,
154    body: &str,
155    headers: Option<Headers>,
156    show_debug: Option<bool>,
157) -> Result<T, E>
158where
159    T: for<'a> Deserialize<'a> + std::fmt::Debug + 'static + From<String>,
160    E: std::fmt::Debug
161        + std::fmt::Display
162        + From<AppError>
163        + From<reqwest::Error>
164        + From<serde_json::Error>,
165{
166    // build request client and get response
167    let response = request_build(base_url, method.clone(), params, body, headers, show_debug).await?;
168    // parse response to json
169    let result: T = parse_response(response).await?;
170    //
171    tracing::info!(
172        "[do_something_with_{:?}] http result: {:?}",
173        method.clone(),
174        result
175    );
176    Ok(result)
177}
178async fn request_build(
179    base_url: &str,
180    method: Method,
181    params: Option<HashMap<String, String>>,
182    body: &str,
183    headers: Option<Headers>,
184    show_debug: Option<bool>,
185) -> Result<Response, AppError> {
186    // set url
187    // let url = set_url(base_url, params).await;
188    tracing::info!(
189        "request url: {}, method: {}, params: {:?}, body ignored",
190        base_url,
191        method.clone(),
192        params.clone()
193    );
194    if show_debug.is_some() && show_debug.unwrap() {
195        tracing::info!("request body: {:?}", body.clone());
196        tracing::info!("request header: {:?}", headers.clone())
197    }
198    // build request client
199    let client = Client::builder().timeout(Duration::from_secs(60)).build()?;
200    // get request
201    let mut request_builder = client.request(method.clone(), base_url);
202    // set query params
203    if !params.is_none() && !params.clone().unwrap().is_empty() {
204        request_builder = request_builder.query(&params);
205    }
206    // set headers
207    if headers.is_some() {
208        let headers = headers.unwrap();
209        // header
210        if headers.header.is_some() {
211            request_builder = set_headers(headers.header.unwrap(), request_builder).await;
212        }
213        // auth
214        if headers.auth.is_some() {
215            request_builder = set_auth(headers.auth.unwrap(), request_builder).await;
216        }
217    }
218
219    // set body
220    if !body.is_empty() {
221        request_builder = request_builder.body(body.to_string());
222    }
223    Ok(request_builder.send().await?)
224}
225
226async fn set_url(base_url: &str, params: &[(&str, &str)]) -> String {
227    let mut url = base_url.to_string();
228    if !params.is_empty() {
229        let str_params = query_to_string(params).await;
230        url = format!("{}?{}", url, str_params);
231    }
232    url
233}
234
235async fn query_to_string(params: &[(&str, &str)]) -> String {
236    let mut query_string = String::new();
237    for (key, value) in params {
238        query_string.push_str(&format!("{}={}&", key, value));
239    }
240    query_string.pop();
241
242    query_string
243}
244
245async fn set_headers(
246    headers: HashMap<String, String>,
247    mut request_builder: RequestBuilder,
248) -> RequestBuilder {
249    let owned_map: Vec<(String, String)> = headers
250        .iter()
251        .map(|(k, v)| (k.to_string(), v.to_string()))
252        .collect();
253    //
254    for (key, value) in owned_map {
255        request_builder = request_builder.header(key, value);
256    }
257    request_builder
258}
259
260async fn set_auth(auth: Auth, mut request_builder: RequestBuilder) -> RequestBuilder {
261    let username = auth.username;
262    let password = auth.password;
263    let token = auth.token;
264    if !username.is_empty() {
265        if !password.is_empty() {
266            request_builder = request_builder.basic_auth(username, Some(password));
267        } else if !token.is_empty() {
268            request_builder = request_builder.bearer_auth(token);
269        }
270    }
271    request_builder
272}