use std::{convert::TryInto, sync::Arc, time::Duration};
use once_cell::sync::Lazy;
use serde::de::DeserializeOwned;
use surf::*;
use crate::prelude::*;
#[allow(dead_code)]
fn logger(
req: Request,
client: Client,
next: middleware::Next,
) -> futures::future::BoxFuture<Result<Response>> {
Box::pin(async move {
let url = req.url().to_string();
let should_log = std::env::var("SCL_HTTP_LOG")
.map(|x| &x == "true")
.unwrap_or(false);
if should_log {
tracing::trace!("[SCL-Core-HTTP] 正在请求 {url}");
}
let res = next.run(req, client).await?;
if let Some(content_type) = res.content_type() {
if should_log {
tracing::trace!(
"[SCL-Core-HTTP] 请求 {} 完成 状态码:{} 响应类型:{}",
url,
res.status(),
content_type
);
if res.status().is_redirection() {
tracing::trace!(
"[SCL-Core-HTTP] 正在重定向至 {}",
res.header("Location").map(|x| x.as_str()).unwrap_or("")
);
}
}
} else if should_log {
tracing::trace!(
"[SCL-Core-HTTP] 请求 {} 完成 状态码:{} 响应类型:无",
url,
res.status()
);
if res.status().is_redirection() {
tracing::trace!(
"[SCL-Core-HTTP] 正在重定向至 {}",
res.header("Location").map(|x| x.as_str()).unwrap_or("")
);
}
}
Ok(res)
})
}
static GLOBAL_CLIENT: Lazy<Arc<Client>> = Lazy::new(|| {
let client = Config::new()
.add_header(
"User-Agent",
"github.com/Steve-xmh/SharpCraftLauncher (stevexmh@qq.com)",
)
.unwrap()
.set_timeout(Some(Duration::from_secs(30)));
let client = if let Ok(mut proxy) = std::env::var("HTTP_PROXY") {
let proxy = if proxy.ends_with('/') {
proxy
} else {
proxy.push('/');
proxy
};
if let Ok(uri) = url::Url::parse(&proxy) {
tracing::trace!("Using http proxy: {uri}");
client.set_base_url(uri)
} else {
client
}
} else {
client
};
let client: Client = client.try_into().unwrap();
Arc::new(client.with(middleware::Redirect::default()))
});
pub async fn retry_future<O, F: std::future::Future<Output = O>>(
max_retries: usize,
future_builder: impl Fn() -> F,
error_handler: impl Fn(&O) -> bool,
) -> DynResult<O> {
let mut retries = 0;
loop {
retries += 1;
let f = future_builder();
let r = f.await;
if error_handler(&r) || retries >= max_retries {
return Ok(r);
}
}
}
pub async fn download(
uris: &[impl AsRef<str> + std::fmt::Debug],
dest_path: &str,
_size: usize,
) -> DynResult {
for uri in uris {
let res = retry_future(5, || get(uri), surf::Result::is_ok).await;
match res {
Ok(Ok(res)) => {
if res.status().is_success() {
let tmp_dest_path = format!("{dest_path}.tmp");
let tmp_file = inner_future::fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&tmp_dest_path)
.await?;
if inner_future::io::copy(res, tmp_file).await.is_ok() {
inner_future::fs::rename(tmp_dest_path, dest_path).await?;
return Ok(());
}
} else {
tracing::trace!("Error {:?} 状态码错误 {}", uri, res.status());
}
}
Ok(Err(e)) => {
tracing::trace!("Error {uri:?} {e}")
}
Err(e) => {
tracing::trace!("Error {uri:?} {e}")
}
}
}
anyhow::bail!(
"轮询下载文件到 {} 失败,请检查你的网络连接,已尝试的链接 {:?}",
dest_path,
uris
)
}
pub async fn retry_get_json<D: DeserializeOwned>(uri: impl AsRef<str>) -> DynResult<D> {
let res = retry_future(5, || get(uri.as_ref()).recv_json(), surf::Result::is_ok).await;
let err = match res {
Ok(Ok(body)) => return Ok(body),
Ok(Err(e)) => {
anyhow::anyhow!("{}", e)
}
Err(e) => e,
};
anyhow::bail!(
"轮询请求链接 {} 失败,请检查你的网络连接:{}",
uri.as_ref(),
err
)
}
pub async fn retry_get_bytes(uri: impl AsRef<str>) -> DynResult<Vec<u8>> {
let res = retry_future(5, || get(uri.as_ref()).recv_bytes(), surf::Result::is_ok).await;
let err = match res {
Ok(Ok(body)) => return Ok(body),
Ok(Err(e)) => {
anyhow::anyhow!("{}", e)
}
Err(e) => e,
};
anyhow::bail!(
"轮询请求链接 {} 失败,请检查你的网络连接:{}",
uri.as_ref(),
err
)
}
pub async fn retry_get_string(uri: impl AsRef<str>) -> DynResult<String> {
let res = retry_future(5, || get(uri.as_ref()).recv_string(), surf::Result::is_ok).await;
let err = match res {
Ok(Ok(body)) => return Ok(body),
Ok(Err(e)) => {
anyhow::anyhow!("{}", e)
}
Err(e) => e,
};
anyhow::bail!(
"轮询请求链接 {} 失败,请检查你的网络连接:{}",
uri.as_ref(),
err
)
}
pub async fn retry_get(uri: impl AsRef<str>) -> DynResult<Response> {
let res = retry_future(5, || get(uri.as_ref()), surf::Result::is_ok).await;
let err = match res {
Ok(Ok(body)) => return Ok(body),
Ok(Err(e)) => {
anyhow::anyhow!(
"{}: {}",
e,
e.backtrace().map(|x| x.to_string()).unwrap_or_default()
)
}
Err(e) => e,
};
anyhow::bail!(
"轮询请求链接 {} 失败,请检查你的网络连接:{}",
uri.as_ref(),
err
)
}
pub fn get(uri: impl AsRef<str>) -> RequestBuilder {
GLOBAL_CLIENT.get(uri)
}
pub fn post(uri: impl AsRef<str>) -> RequestBuilder {
GLOBAL_CLIENT.post(uri)
}
#[derive(Debug, Clone)]
pub enum RequestResult<T> {
Ok(T),
Err(crate::auth::structs::mojang::ErrorResponse),
}
pub mod no_retry {
use serde::{de::DeserializeOwned, Serialize};
pub use surf::get;
use super::RequestResult;
use crate::prelude::DynResult;
pub async fn get_data<D: DeserializeOwned>(uri: &str) -> DynResult<RequestResult<D>> {
let result = surf::get(uri)
.middleware(surf::middleware::Redirect::default())
.recv_string()
.await
.map_err(|e| anyhow::anyhow!("无法接收来自 {} 的响应:{:?}", uri, e))?;
if let Ok(result) = serde_json::from_str(&result) {
Ok(RequestResult::Ok(result))
} else {
let result = serde_json::from_str(&result)?;
Ok(RequestResult::Err(result))
}
}
pub async fn post_data<D: DeserializeOwned, S: Serialize + std::fmt::Debug>(
uri: &str,
body: &S,
) -> DynResult<RequestResult<D>> {
let result = surf::post(uri)
.header("Content-Type", "application/json; charset=utf-8")
.body_json(body)
.map_err(|e| anyhow::anyhow!("无法解析请求主体给 {}:{:?}", uri, e))?
.recv_string()
.await
.map_err(|e| anyhow::anyhow!("无法接收来自 {} 的响应:{:?}", uri, e))?;
if let Ok(result) = serde_json::from_str(&result) {
Ok(RequestResult::Ok(result))
} else {
let result = serde_json::from_str(&result)?;
Ok(RequestResult::Err(result))
}
}
}