use reqwest::{Client, RequestBuilder, Response, StatusCode, header};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use signer_core::{SignerJWT, SignerJWTClaims, SignerJWTHeader, SignerUser};
#[derive(Clone)]
pub struct HttpClientConfig {
pub base_path: String,
pub auth: Option<HttpClientAuth>,
}
#[derive(Debug, Clone)]
pub struct HttpClientAuth {
pub user: SignerUser,
pub expire_duration: chrono::Duration,
}
impl HttpClientConfig {
pub fn new(user: SignerUser, base_path: String) -> Self {
Self {
base_path,
auth: Some(HttpClientAuth {
user,
expire_duration: chrono::Duration::minutes(5),
}),
}
}
pub fn new_no_auth(base_path: String) -> Self {
Self {
base_path,
auth: None,
}
}
pub fn with_base_path(&self, base_path: String) -> Self {
let mut new_config = self.clone();
new_config.base_path = base_path;
new_config
}
pub fn expire(mut self, expire_duration: chrono::Duration) -> Self {
if let Some(auth) = &mut self.auth {
auth.expire_duration = expire_duration;
}
self
}
}
#[derive(Clone)]
pub struct HttpClient {
client: Client,
config: HttpClientConfig,
}
impl HttpClient {
pub fn new(config: HttpClientConfig) -> Self {
let client = Client::builder()
.user_agent("SignerDaemon Client v0.3.1")
.build()
.expect("创建 HTTP 客户端失败");
Self { client, config }
}
fn get_auth_header(&self) -> crate::DaemonResult<Option<String>> {
if let Some(auth) = &self.config.auth {
let jwt = SignerJWT::new(
SignerJWTHeader::default(&auth.user),
SignerJWTClaims::default(
&auth.user,
self.config.base_path.clone(),
uuid::Uuid::new_v4().to_string(),
)
.with_expired_duration(auth.expire_duration),
)
.encode(&auth.user)
.map_err(|e| {
crate::DaemonError::Signer(crate::SignerError::Msg(format!(
"编码 JWT 字符串失败: {}",
e
)))
})?;
Ok(Some(format!("Bearer {}", jwt)))
} else {
Ok(None)
}
}
fn request(&self, method: reqwest::Method, path: &str) -> crate::DaemonResult<RequestBuilder> {
let url = format!("{}{}", self.config.base_path, path);
let mut builder = self.client.request(method, url);
if let Some(auth) = self.get_auth_header()? {
builder = builder.header(header::AUTHORIZATION, auth);
}
Ok(builder)
}
pub async fn get<T: DeserializeOwned>(&self, path: &str) -> crate::DaemonResult<T> {
let response = self
.request(reqwest::Method::GET, path)?
.send()
.await
.map_err(|e| {
crate::DaemonError::Signer(crate::SignerError::Msg(format!(
"发送 GET 请求失败: {}",
e
)))
})?;
self.process_response(response).await
}
pub async fn get_raw(&self, path: &str) -> crate::DaemonResult<Response> {
let response = self
.request(reqwest::Method::GET, path)?
.send()
.await
.map_err(|e| {
crate::DaemonError::Signer(crate::SignerError::Msg(format!(
"发送 GET 请求失败: {}",
e
)))
})?;
Ok(response)
}
pub async fn get_with_query<T: DeserializeOwned, Q: Serialize>(
&self,
path: &str,
query: &Q,
) -> crate::DaemonResult<T> {
let response = self
.request(reqwest::Method::GET, path)?
.query(query)
.send()
.await
.map_err(|e| {
crate::DaemonError::Signer(crate::SignerError::Msg(format!(
"发送带查询参数的 GET 请求失败: {}",
e
)))
})?;
self.process_response(response).await
}
pub async fn head(&self, path: &str) -> crate::DaemonResult<Response> {
let response = self
.request(reqwest::Method::HEAD, path)?
.send()
.await
.map_err(|e| {
crate::DaemonError::Signer(crate::SignerError::Msg(format!(
"发送 HEAD 请求失败: {}",
e
)))
})?;
Ok(response)
}
pub async fn post<T: DeserializeOwned, B: Serialize>(
&self,
path: &str,
body: &B,
) -> crate::DaemonResult<T> {
let response = self
.request(reqwest::Method::POST, path)?
.json(body)
.send()
.await
.map_err(|e| {
crate::DaemonError::Signer(crate::SignerError::Msg(format!(
"发送 POST 请求失败: {}",
e
)))
})?;
self.process_response(response).await
}
pub async fn post_raw<B: Serialize>(
&self,
path: &str,
body: &B,
) -> crate::DaemonResult<Response> {
let response = self
.request(reqwest::Method::POST, path)?
.json(body)
.send()
.await
.map_err(|e| {
crate::DaemonError::Signer(crate::SignerError::Msg(format!(
"发送 POST 请求失败: {}",
e
)))
})?;
Ok(response)
}
pub async fn patch<T: DeserializeOwned, B: Serialize>(
&self,
path: &str,
body: &B,
) -> crate::DaemonResult<T> {
let response = self
.request(reqwest::Method::PATCH, path)?
.json(body)
.send()
.await
.map_err(|e| {
crate::DaemonError::Signer(crate::SignerError::Msg(format!(
"发送 PATCH 请求失败: {}",
e
)))
})?;
self.process_response(response).await
}
pub async fn post_multipart(
&self,
path: &str,
form: reqwest::multipart::Form,
) -> crate::DaemonResult<Response> {
let response = self
.request(reqwest::Method::POST, path)?
.multipart(form)
.send()
.await
.map_err(|e| {
crate::DaemonError::Signer(crate::SignerError::Msg(format!(
"发送 POST multipart 请求失败: {}",
e
)))
})?;
Ok(response)
}
async fn process_response<T: DeserializeOwned>(
&self,
response: Response,
) -> crate::DaemonResult<T> {
if response.status().is_success() {
let result = response.json::<T>().await.map_err(|e| {
crate::DaemonError::Signer(crate::SignerError::Msg(format!(
"解析响应 JSON 失败: {}",
e
)))
})?;
Ok(result)
} else {
let status = response.status();
let body = response
.text()
.await
.unwrap_or_else(|_| "无法读取响应体".to_string());
Err(crate::DaemonError::Signer(crate::SignerError::Msg(
format!("HTTP 请求失败: 状态码 {}, 响应体: {}", status, body),
)))
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum HttpClientError {
#[error("请求错误: {0}")]
RequestError(#[from] reqwest::Error),
#[error("响应错误: 状态码 {status}, 内容: {content}")]
ResponseError {
status: StatusCode,
content: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiResponse<T> {
pub data: T,
}