scl_core/
http.rs

1//! HTTP 包装,虽然是内部使用但是你也可以使用这个来做点 HTTP 请求什么的
2//!
3//! 或者在二次开发的时候更换成你喜欢的版本
4
5use std::{convert::TryInto, sync::Arc, time::Duration};
6
7use once_cell::sync::Lazy;
8use serde::de::DeserializeOwned;
9use surf::*;
10
11use crate::prelude::*;
12
13#[allow(dead_code)]
14fn logger(
15    req: Request,
16    client: Client,
17    next: middleware::Next,
18) -> futures::future::BoxFuture<Result<Response>> {
19    Box::pin(async move {
20        let url = req.url().to_string();
21        let should_log = std::env::var("SCL_HTTP_LOG")
22            .map(|x| &x == "true")
23            .unwrap_or(false);
24        if should_log {
25            println!("[SCL-Core-HTTP] 正在请求 {url}");
26        }
27        let res = next.run(req, client).await?;
28        if let Some(content_type) = res.content_type() {
29            if should_log {
30                println!(
31                    "[SCL-Core-HTTP] 请求 {} 完成 状态码:{} 响应类型:{}",
32                    url,
33                    res.status(),
34                    content_type
35                );
36                if res.status().is_redirection() {
37                    println!(
38                        "[SCL-Core-HTTP] 正在重定向至 {}",
39                        res.header("Location").map(|x| x.as_str()).unwrap_or("")
40                    );
41                }
42            }
43        } else if should_log {
44            println!(
45                "[SCL-Core-HTTP] 请求 {} 完成 状态码:{} 响应类型:无",
46                url,
47                res.status()
48            );
49            if res.status().is_redirection() {
50                println!(
51                    "[SCL-Core-HTTP] 正在重定向至 {}",
52                    res.header("Location").map(|x| x.as_str()).unwrap_or("")
53                );
54            }
55        }
56        Ok(res)
57    })
58}
59
60static GLOBAL_CLIENT: Lazy<Arc<Client>> = Lazy::new(|| {
61    let client = Config::new()
62        .add_header(
63            "User-Agent",
64            "github.com/Steve-xmh/SharpCraftLauncher (stevexmh@qq.com)",
65        )
66        .unwrap()
67        .set_timeout(Some(Duration::from_secs(30)));
68    let client = if let Ok(mut proxy) = std::env::var("HTTP_PROXY") {
69        let proxy = if proxy.ends_with('/') {
70            proxy
71        } else {
72            proxy.push('/');
73            proxy
74        };
75        if let Ok(uri) = url::Url::parse(&proxy) {
76            println!("Using http proxy: {uri}");
77            client.set_base_url(uri)
78        } else {
79            client
80        }
81    } else {
82        client
83    };
84    let client: Client = client.try_into().unwrap();
85    Arc::new(client.with(middleware::Redirect::default()))
86});
87
88/// Future 重试调用函数,为下载文件失败重试而准备
89///
90/// 主要是 surf 库不带重试功能,中间件写了也有大堆问题。。。
91pub async fn retry_future<O, F: std::future::Future<Output = O>>(
92    max_retries: usize,
93    future_builder: impl Fn() -> F,
94    error_handler: impl Fn(&O) -> bool,
95) -> DynResult<O> {
96    let mut retries = 0;
97    loop {
98        retries += 1;
99        let f = future_builder();
100        let r = f.await;
101        if error_handler(&r) || retries >= max_retries {
102            return Ok(r);
103        }
104    }
105}
106
107/// 根据所给链接,依次尝试请求下载
108///
109/// 启发自 PCL1 源代码
110///
111/// TODO: 如 size 参数为非零值,则将会使用分片下载
112pub async fn download(
113    uris: &[impl AsRef<str> + std::fmt::Debug],
114    dest_path: &str,
115    _size: usize,
116) -> DynResult {
117    for uri in uris {
118        // 尝试重试两次,都失败的话就换下一个链接
119        let res = retry_future(5, || get(uri), surf::Result::is_ok).await;
120        match res {
121            Ok(Ok(res)) => {
122                if res.status().is_success() {
123                    let tmp_dest_path = format!("{dest_path}.tmp");
124                    let tmp_file = inner_future::fs::OpenOptions::new()
125                        .create(true)
126                        .write(true)
127                        .truncate(true)
128                        .open(&tmp_dest_path)
129                        .await?;
130                    if inner_future::io::copy(res, tmp_file).await.is_ok() {
131                        inner_future::fs::rename(tmp_dest_path, dest_path).await?;
132                        return Ok(());
133                    }
134                } else {
135                    println!("Error {:?} 状态码错误 {}", uri, res.status());
136                }
137            }
138            Ok(Err(e)) => {
139                println!("Error {uri:?} {e}")
140            }
141            Err(e) => {
142                println!("Error {uri:?} {e}")
143            }
144        }
145    }
146    anyhow::bail!(
147        "轮询下载文件到 {} 失败,请检查你的网络连接,已尝试的链接 {:?}",
148        dest_path,
149        uris
150    )
151}
152
153/// 重试获取 JSON 对象
154///
155/// 返回的数据结构需要实现 [`serde::de::DeserializeOwned`]
156pub async fn retry_get_json<D: DeserializeOwned>(uri: impl AsRef<str>) -> DynResult<D> {
157    let res = retry_future(5, || get(uri.as_ref()).recv_json(), surf::Result::is_ok).await;
158    let err = match res {
159        Ok(Ok(body)) => return Ok(body),
160        Ok(Err(e)) => {
161            anyhow::anyhow!("{}", e)
162        }
163        Err(e) => e,
164    };
165    anyhow::bail!(
166        "轮询请求链接 {} 失败,请检查你的网络连接:{}",
167        uri.as_ref(),
168        err
169    )
170}
171
172/// 重试获取数据
173pub async fn retry_get_bytes(uri: impl AsRef<str>) -> DynResult<Vec<u8>> {
174    let res = retry_future(5, || get(uri.as_ref()).recv_bytes(), surf::Result::is_ok).await;
175    let err = match res {
176        Ok(Ok(body)) => return Ok(body),
177        Ok(Err(e)) => {
178            anyhow::anyhow!("{}", e)
179        }
180        Err(e) => e,
181    };
182    anyhow::bail!(
183        "轮询请求链接 {} 失败,请检查你的网络连接:{}",
184        uri.as_ref(),
185        err
186    )
187}
188
189/// 重试获取字符串
190pub async fn retry_get_string(uri: impl AsRef<str>) -> DynResult<String> {
191    let res = retry_future(5, || get(uri.as_ref()).recv_string(), surf::Result::is_ok).await;
192    let err = match res {
193        Ok(Ok(body)) => return Ok(body),
194        Ok(Err(e)) => {
195            anyhow::anyhow!("{}", e)
196        }
197        Err(e) => e,
198    };
199    anyhow::bail!(
200        "轮询请求链接 {} 失败,请检查你的网络连接:{}",
201        uri.as_ref(),
202        err
203    )
204}
205
206/// 重试获取响应,当取得成功时返回
207///
208/// 你可能需要自行确认状态码是否成功
209pub async fn retry_get(uri: impl AsRef<str>) -> DynResult<Response> {
210    let res = retry_future(5, || get(uri.as_ref()), surf::Result::is_ok).await;
211    let err = match res {
212        Ok(Ok(body)) => return Ok(body),
213        Ok(Err(e)) => {
214            anyhow::anyhow!(
215                "{}: {}",
216                e,
217                e.backtrace().map(|x| x.to_string()).unwrap_or_default()
218            )
219        }
220        Err(e) => e,
221    };
222    anyhow::bail!(
223        "轮询请求链接 {} 失败,请检查你的网络连接:{}",
224        uri.as_ref(),
225        err
226    )
227}
228
229/// 生成简单的 GET 请求
230pub fn get(uri: impl AsRef<str>) -> RequestBuilder {
231    GLOBAL_CLIENT.get(uri)
232}
233
234/// 生成简单的 POST 请求
235pub fn post(uri: impl AsRef<str>) -> RequestBuilder {
236    GLOBAL_CLIENT.post(uri)
237}
238
239/// 针对 Mojang 验证 API 的响应结构
240#[derive(Debug, Clone)]
241pub enum RequestResult<T> {
242    /// 返回的结构是成功的,此处为实际数据
243    Ok(T),
244    /// 返回的结构是错误的,此处为错误信息结构
245    Err(crate::auth::structs::mojang::ErrorResponse),
246}
247
248/// 不会进行重试的 HTTP 请求模块
249pub mod no_retry {
250    use serde::{de::DeserializeOwned, Serialize};
251    pub use surf::get;
252
253    use super::RequestResult;
254    use crate::prelude::DynResult;
255
256    /// 获取 JSON 对象
257    ///
258    /// 返回的数据结构需要实现 [`serde::de::DeserializeOwned`]
259    pub async fn get_data<D: DeserializeOwned>(uri: &str) -> DynResult<RequestResult<D>> {
260        let result = surf::get(uri)
261            .middleware(surf::middleware::Redirect::default())
262            .recv_string()
263            .await
264            .map_err(|e| anyhow::anyhow!("无法接收来自 {} 的响应:{:?}", uri, e))?;
265        if let Ok(result) = serde_json::from_str(&result) {
266            Ok(RequestResult::Ok(result))
267        } else {
268            let result = serde_json::from_str(&result)?;
269            Ok(RequestResult::Err(result))
270        }
271    }
272
273    /// 带请求体去获取 JSON 对象
274    ///
275    /// 传入的请求体需要实现 [`serde::ser::Serialize`] 和 [`std::fmt::Debug`]
276    ///
277    /// 返回的数据结构需要实现 [`serde::de::DeserializeOwned`]
278    pub async fn post_data<D: DeserializeOwned, S: Serialize + std::fmt::Debug>(
279        uri: &str,
280        body: &S,
281    ) -> DynResult<RequestResult<D>> {
282        let result = surf::post(uri)
283            .header("Content-Type", "application/json; charset=utf-8")
284            .body_json(body)
285            .map_err(|e| anyhow::anyhow!("无法解析请求主体给 {}:{:?}", uri, e))?
286            .recv_string()
287            .await
288            .map_err(|e| anyhow::anyhow!("无法接收来自 {} 的响应:{:?}", uri, e))?;
289        if let Ok(result) = serde_json::from_str(&result) {
290            Ok(RequestResult::Ok(result))
291        } else {
292            let result = serde_json::from_str(&result)?;
293            Ok(RequestResult::Err(result))
294        }
295    }
296}