使用tauri的时候在rust代码中发起http请求,分享一个tauri插件tauri-plugin-http的工具类:
同时分享一个自己写的小工具,使用tauri2+开发的粘贴板工具:https://jingchuanyuexiang.com
- GitHub: https://github.com/1750777402/ClipPal
- Gitee: https://gitee.com/ygz123/clip-pal
下面是工具类代码,复制即用:
#![allow(dead_code)]
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tauri_plugin_http::{
reqwest,
reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue},
};
/// 统一API响应结构体
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiResponse<T> {
pub code: i32,
pub message: String,
pub data: Option<T>,
pub timestamp: i64,
}
/// 原始HTTP响应结构体(用于任意返回格式)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RawResponse<T> {
pub status: u16,
pub headers: HashMap<String, String>,
pub data: T,
pub url: String,
}
/// HTTP请求配置
#[derive(Debug, Clone)]
pub struct HttpConfig {
pub timeout: Option<u64>,
pub headers: Option<HashMap<String, String>>,
pub user_agent: Option<String>,
}
impl Default for HttpConfig {
fn default() -> Self {
Self {
timeout: Some(30),
headers: None,
user_agent: Some("ClipPal/1.0".to_string()),
}
}
}
/// HTTP请求错误
#[derive(Debug, thiserror::Error)]
pub enum HttpError {
#[error("请求失败: {0}")]
RequestFailed(String),
#[error("序列化失败: {0}")]
SerializationFailed(String),
#[error("反序列化失败: {0}")]
DeserializationFailed(String),
#[error("无效的URL: {0}")]
InvalidUrl(String),
#[error("超时: {0}")]
Timeout(String),
#[error("网络错误: {0}")]
NetworkError(String),
#[error("全局AppHandle获取失败")]
AppHandleNotFound,
}
/// HTTP客户端
pub struct HttpClient {
config: HttpConfig,
}
impl HttpClient {
/// 创建新的HTTP客户端
pub fn new() -> Self {
Self {
config: HttpConfig::default(),
}
}
/// 使用自定义配置创建HTTP客户端
pub fn with_config(config: HttpConfig) -> Self {
Self { config }
}
/// 设置请求配置
pub fn set_config(mut self, config: HttpConfig) -> Self {
self.config = config;
self
}
/// 设置超时时间
pub fn timeout(mut self, timeout: u64) -> Self {
self.config.timeout = Some(timeout);
self
}
/// 设置请求头
pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
self.config.headers = Some(headers);
self
}
/// 设置User-Agent
pub fn user_agent(mut self, user_agent: String) -> Self {
self.config.user_agent = Some(user_agent);
self
}
/// 发起GET请求(返回ApiResponse格式)
pub async fn get<T>(&self, url: &str) -> Result<ApiResponse<T>, HttpError>
where
T: for<'de> Deserialize<'de>,
{
self.request::<(), T>("GET", url, None, None).await
}
/// 发起带查询参数的GET请求(返回ApiResponse格式)
pub async fn get_with_params<T>(
&self,
url: &str,
params: &HashMap<String, String>,
) -> Result<ApiResponse<T>, HttpError>
where
T: for<'de> Deserialize<'de>,
{
let url_with_params = self.build_url_with_params(url, params)?;
self.request::<(), T>("GET", &url_with_params, None, None)
.await
}
/// 发起POST请求(返回ApiResponse格式)
pub async fn post<T, U>(&self, url: &str, data: Option<&T>) -> Result<ApiResponse<U>, HttpError>
where
T: Serialize,
U: for<'de> Deserialize<'de>,
{
self.request("POST", url, data, None).await
}
/// 发起带JSON数据的POST请求(返回ApiResponse格式)
pub async fn post_json<T, U>(&self, url: &str, data: &T) -> Result<ApiResponse<U>, HttpError>
where
T: Serialize,
U: for<'de> Deserialize<'de>,
{
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "application/json".to_string());
self.request_with_headers("POST", url, Some(data), Some(headers))
.await
}
/// 发起带表单数据的POST请求(返回ApiResponse格式)
pub async fn post_form<T, U>(
&self,
url: &str,
data: &HashMap<String, String>,
) -> Result<ApiResponse<U>, HttpError>
where
U: for<'de> Deserialize<'de>,
{
let mut headers = HashMap::new();
headers.insert(
"Content-Type".to_string(),
"application/x-www-form-urlencoded".to_string(),
);
self.request_with_headers("POST", url, Some(data), Some(headers))
.await
}
/// 发起带自定义请求头的请求(返回ApiResponse格式)
pub async fn request_with_headers<T, U>(
&self,
method: &str,
url: &str,
data: Option<&T>,
headers: Option<HashMap<String, String>>,
) -> Result<ApiResponse<U>, HttpError>
where
T: Serialize,
U: for<'de> Deserialize<'de>,
{
let mut all_headers = self.config.headers.clone().unwrap_or_default();
if let Some(custom_headers) = headers {
all_headers.extend(custom_headers);
}
self.request(method, url, data, Some(all_headers)).await
}
// ========== 通用HTTP请求方法(返回任意格式) ==========
/// 发起GET请求(返回原始响应格式)
pub async fn get_raw<T>(&self, url: &str) -> Result<RawResponse<T>, HttpError>
where
T: for<'de> Deserialize<'de>,
{
self.request_raw::<(), T>("GET", url, None, None).await
}
/// 发起带查询参数的GET请求(返回原始响应格式)
pub async fn get_with_params_raw<T>(
&self,
url: &str,
params: &HashMap<String, String>,
) -> Result<RawResponse<T>, HttpError>
where
T: for<'de> Deserialize<'de>,
{
let url_with_params = self.build_url_with_params(url, params)?;
self.request_raw::<(), T>("GET", &url_with_params, None, None)
.await
}
/// 发起POST请求(返回原始响应格式)
pub async fn post_raw<T, U>(
&self,
url: &str,
data: Option<&T>,
) -> Result<RawResponse<U>, HttpError>
where
T: Serialize,
U: for<'de> Deserialize<'de>,
{
self.request_raw("POST", url, data, None).await
}
/// 发起带JSON数据的POST请求(返回原始响应格式)
pub async fn post_json_raw<T, U>(
&self,
url: &str,
data: &T,
) -> Result<RawResponse<U>, HttpError>
where
T: Serialize,
U: for<'de> Deserialize<'de>,
{
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "application/json".to_string());
self.request_with_headers_raw("POST", url, Some(data), Some(headers))
.await
}
/// 发起带表单数据的POST请求(返回原始响应格式)
pub async fn post_form_raw<T, U>(
&self,
url: &str,
data: &HashMap<String, String>,
) -> Result<RawResponse<U>, HttpError>
where
U: for<'de> Deserialize<'de>,
{
let mut headers = HashMap::new();
headers.insert(
"Content-Type".to_string(),
"application/x-www-form-urlencoded".to_string(),
);
self.request_with_headers_raw("POST", url, Some(data), Some(headers))
.await
}
/// 发起带自定义请求头的请求(返回原始响应格式)
pub async fn request_with_headers_raw<T, U>(
&self,
method: &str,
url: &str,
data: Option<&T>,
headers: Option<HashMap<String, String>>,
) -> Result<RawResponse<U>, HttpError>
where
T: Serialize,
U: for<'de> Deserialize<'de>,
{
let mut all_headers = self.config.headers.clone().unwrap_or_default();
if let Some(custom_headers) = headers {
all_headers.extend(custom_headers);
}
self.request_raw(method, url, data, Some(all_headers)).await
}
/// 核心请求方法(返回ApiResponse格式)
async fn request<T, U>(
&self,
method: &str,
url: &str,
data: Option<&T>,
headers: Option<HashMap<String, String>>,
) -> Result<ApiResponse<U>, HttpError>
where
T: Serialize,
U: for<'de> Deserialize<'de>,
{
let response_text = self.execute_request(method, url, data, headers).await?;
// 直接反序列化为ApiResponse<U>
let api_response: ApiResponse<U> = serde_json::from_str(&response_text).map_err(|e| {
HttpError::DeserializationFailed(format!("反序列化ApiResponse失败: {}", e))
})?;
Ok(api_response)
}
/// 核心请求方法(返回原始响应格式)
async fn request_raw<T, U>(
&self,
method: &str,
url: &str,
data: Option<&T>,
headers: Option<HashMap<String, String>>,
) -> Result<RawResponse<U>, HttpError>
where
T: Serialize,
U: for<'de> Deserialize<'de>,
{
let (status, response_url, response_headers, response_text) = self
.execute_request_with_response(method, url, data, headers)
.await?;
// 读取响应头
let mut headers_map = HashMap::new();
for (key, value) in response_headers.iter() {
if let Ok(value_str) = value.to_str() {
headers_map.insert(key.as_str().to_string(), value_str.to_string());
}
}
// 反序列化响应数据
let response_data = if response_text.is_empty() {
serde_json::from_str("null").map_err(|e| {
HttpError::DeserializationFailed(format!("反序列化空响应失败: {}", e))
})?
} else {
serde_json::from_str(&response_text)
.map_err(|e| HttpError::DeserializationFailed(format!("反序列化响应失败: {}", e)))?
};
Ok(RawResponse {
status,
headers: headers_map,
data: response_data,
url: response_url,
})
}
/// 执行HTTP请求并返回响应文本(公共方法)
async fn execute_request<T>(
&self,
method: &str,
url: &str,
data: Option<&T>,
headers: Option<HashMap<String, String>>,
) -> Result<String, HttpError>
where
T: Serialize,
{
let (_, _, _, response_text) = self
.execute_request_with_response(method, url, data, headers)
.await?;
Ok(response_text)
}
/// 执行HTTP请求并返回响应信息(公共方法)
async fn execute_request_with_response<T>(
&self,
method: &str,
url: &str,
data: Option<&T>,
headers: Option<HashMap<String, String>>,
) -> Result<(u16, String, reqwest::header::HeaderMap, String), HttpError>
where
T: Serialize,
{
// 打印请求信息(debug级别)
log::debug!("=== HTTP请求开始 ===");
log::debug!("请求方法: {}", method);
log::debug!("请求URL: {}", url);
// 验证URL
let _parsed_url = reqwest::Url::parse(url)
.map_err(|e| {
let error_msg = format!("无效的URL: {}", e);
log::error!("URL验证失败: {}", error_msg);
HttpError::InvalidUrl(error_msg)
})?;
// 构建请求体
let body = if let Some(data) = data {
serde_json::to_string(data)
.map_err(|e| {
let error_msg = format!("序列化请求数据失败: {}", e);
log::error!("请求数据序列化失败: {}", error_msg);
HttpError::SerializationFailed(error_msg)
})?
} else {
String::new()
};
// 打印请求体信息(debug级别)
if !body.is_empty() {
log::debug!("请求体: {}", body);
} else {
log::debug!("请求体: 空");
}
// 构建请求头
let mut header_map = HeaderMap::new();
// 设置默认User-Agent
if let Some(user_agent) = &self.config.user_agent {
header_map.insert(
"User-Agent",
HeaderValue::from_str(user_agent)
.map_err(|e| {
let error_msg = format!("无效的User-Agent: {}", e);
log::error!("User-Agent设置失败: {}", error_msg);
HttpError::RequestFailed(error_msg)
})?,
);
log::debug!("User-Agent: {}", user_agent);
}
// 设置Content-Type
if !body.is_empty() && method != "GET" {
header_map.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
log::debug!("Content-Type: application/json");
}
// 设置自定义请求头
if let Some(custom_headers) = headers {
log::debug!("自定义请求头:");
for (key, value) in &custom_headers {
let header_name = HeaderName::from_lowercase(key.to_lowercase().as_bytes())
.map_err(|e| {
let error_msg = format!("无效的请求头名称: {}", e);
log::error!("请求头名称无效: {}", error_msg);
HttpError::RequestFailed(error_msg)
})?;
header_map.insert(
header_name,
HeaderValue::from_str(value)
.map_err(|e| {
let error_msg = format!("无效的请求头值: {}", e);
log::error!("请求头值无效: {}", error_msg);
HttpError::RequestFailed(error_msg)
})?,
);
log::debug!(" {}: {}", key, value);
}
}
// 构建请求选项
let mut options = tauri_plugin_http::reqwest::ClientBuilder::new();
// 设置超时
if let Some(timeout) = self.config.timeout {
options = options.timeout(std::time::Duration::from_secs(timeout));
log::debug!("请求超时设置: {}秒", timeout);
}
// 发起请求
let client = options
.build()
.map_err(|e| {
let error_msg = format!("创建HTTP客户端失败: {}", e);
log::error!("HTTP客户端创建失败: {}", error_msg);
HttpError::RequestFailed(error_msg)
})?;
let request_builder = match method.to_uppercase().as_str() {
"GET" => client.get(url),
"POST" => client.post(url).body(body),
"PUT" => client.put(url).body(body),
"DELETE" => client.delete(url),
"PATCH" => client.patch(url).body(body),
_ => {
let error_msg = format!("不支持的HTTP方法: {}", method);
log::error!("不支持的HTTP方法: {}", error_msg);
return Err(HttpError::RequestFailed(error_msg));
}
};
let request = request_builder
.headers(header_map)
.build()
.map_err(|e| {
let error_msg = format!("构建请求失败: {}", e);
log::error!("请求构建失败: {}", error_msg);
HttpError::RequestFailed(error_msg)
})?;
log::debug!("=== 开始执行HTTP请求 ===");
// 执行请求
let response = client
.execute(request)
.await
.map_err(|e| {
let error_msg = format!("网络请求失败: {}", e);
log::error!("网络请求执行失败: {}", error_msg);
log::error!("错误详情: {:?}", e);
HttpError::NetworkError(error_msg)
})?;
let status = response.status().as_u16();
let response_url = response.url().to_string();
let response_headers = response.headers().clone();
log::debug!("=== HTTP响应信息 ===");
log::debug!("响应状态码: {}", status);
log::debug!("响应URL: {}", response_url);
log::debug!("响应头数量: {}", response_headers.len());
// 打印响应头信息(debug级别)
for (name, value) in response_headers.iter() {
if let Ok(value_str) = value.to_str() {
log::debug!("响应头 {}: {}", name, value_str);
}
}
let response_text = response
.text()
.await
.map_err(|e| {
let error_msg = format!("读取响应失败: {}", e);
log::error!("响应内容读取失败: {}", error_msg);
log::error!("错误详情: {:?}", e);
HttpError::NetworkError(error_msg)
})?;
// 根据状态码决定日志级别
match status {
200..=299 => {
// 成功状态码 (2xx)
log::debug!("=== HTTP请求成功 ===");
log::debug!("状态码: {} (成功)", status);
log::debug!("响应内容长度: {} 字符", response_text.len());
// 如果响应内容太长,只打印前500个字符
if response_text.len() > 500 {
log::debug!("响应内容预览: {}...", &response_text[..500]);
} else {
log::debug!("响应内容: {}", response_text);
}
}
300..=399 => {
// 重定向状态码 (3xx)
log::debug!("=== HTTP请求重定向 ===");
log::debug!("状态码: {} (重定向)", status);
log::debug!("响应内容: {}", response_text);
}
400..=499 => {
// 客户端错误状态码 (4xx)
log::error!("=== HTTP请求失败 (客户端错误) ===");
log::error!("状态码: {} (客户端错误)", status);
log::error!("响应内容: {}", response_text);
}
500..=599 => {
// 服务器错误状态码 (5xx)
log::error!("=== HTTP请求失败 (服务器错误) ===");
log::error!("状态码: {} (服务器错误)", status);
log::error!("响应内容: {}", response_text);
}
_ => {
// 其他未知状态码
log::warn!("=== HTTP请求未知状态 ===");
log::warn!("状态码: {} (未知)", status);
log::warn!("响应内容: {}", response_text);
}
}
Ok((status, response_url, response_headers, response_text))
}
/// 构建带查询参数的URL
fn build_url_with_params(
&self,
base_url: &str,
params: &HashMap<String, String>,
) -> Result<String, HttpError> {
let mut url = reqwest::Url::parse(base_url)
.map_err(|e| HttpError::InvalidUrl(format!("无效的URL: {}", e)))?;
for (key, value) in params {
url.query_pairs_mut().append_pair(key, value);
}
Ok(url.to_string())
}
}
/// 返回ApiResponse格式的HTTP请求函数
pub async fn get<T>(url: &str) -> Result<ApiResponse<T>, HttpError>
where
T: for<'de> Deserialize<'de>,
{
HttpClient::new().get(url).await
}
pub async fn post<T, U>(url: &str, data: &T) -> Result<ApiResponse<U>, HttpError>
where
T: Serialize,
U: for<'de> Deserialize<'de>,
{
HttpClient::new().post_json(url, data).await
}
/// 便捷的HTTP请求函数(返回原始响应格式)
pub async fn get_raw<T>(url: &str) -> Result<RawResponse<T>, HttpError>
where
T: for<'de> Deserialize<'de>,
{
HttpClient::new().get_raw(url).await
}
pub async fn post_raw<T, U>(url: &str, data: &T) -> Result<RawResponse<U>, HttpError>
where
T: Serialize,
U: for<'de> Deserialize<'de>,
{
HttpClient::new().post_json_raw(url, data).await
}