当前位置: 首页 > news >正文

分享一个tauri-plugin-http的工具类

使用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-Agentpub 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>whereT: 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>whereT: 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>whereT: 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>whereT: 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>whereU: 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>whereT: 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>whereT: 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>whereT: 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>whereT: 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>whereT: 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>whereU: 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>whereT: 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>whereT: 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>whereT: 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>whereT: 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>whereT: Serialize,{// 打印请求信息(debug级别)log::debug!("=== HTTP请求开始 ===");log::debug!("请求方法: {}", method);log::debug!("请求URL: {}", url);// 验证URLlet _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-Agentif 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-Typeif !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))}/// 构建带查询参数的URLfn 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>
whereT: for<'de> Deserialize<'de>,
{HttpClient::new().get(url).await
}pub async fn post<T, U>(url: &str, data: &T) -> Result<ApiResponse<U>, HttpError>
whereT: 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>
whereT: 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>
whereT: Serialize,U: for<'de> Deserialize<'de>,
{HttpClient::new().post_json_raw(url, data).await
}
http://www.dtcms.com/a/293912.html

相关文章:

  • python3写一个异步流式 http 接口服务调用大模型(async, stream, sanic)---6.2
  • 基于数据挖掘的短视频点赞影响因素分析【LightGBM、XGBoost、随机森林、smote】
  • 探索大语言模型(LLM):提升 RAG 性能的全方位优化策略
  • 深圳TCL外包岗位要去吗?
  • NLP基础全面解析:从概念到实践
  • 主要分布在背侧海马体(dHPC)CA1区域(dCA1)的时间细胞对NLP中的深层语义分析的积极影响和启示
  • WebGIS 中常用空间数据格式
  • Linux网络:网络层-IP协议
  • 金仓 KEMCC 非云环境初始化与纳管指南
  • 每日一算:华为-批萨分配问题
  • 异常的传递性|模块的概念和导入|自定义模块并导入
  • Nginx + PM2 实现Express API + React 前端 本地测试服务器搭建
  • 从 Shell 脚本到 Go 应用:使用 Kiro AI 助手完成 Harpoon 项目重构的完整实践
  • [特殊字符] 从数据库无法访问到成功修复崩溃表:一次 MySQL 故障排查实录
  • 闲庭信步使用图像验证平台加速FPGA的开发:第三十二课——车牌识别的FPGA实现(4)车牌字符的分割定位
  • 基于Tornado的WebSocket实时聊天系统:从零到一构建与解析
  • 简单理解现代Web应用架构:从简单到企业级
  • 棱镜技术在光谱相机中应用
  • 【Unity实战100例】Unity资源下载系统开发流程详解(移动端、PC端 ,局域网控制台服务)
  • K8s:离线部署Kubernetes1.26.12及采用外部Harbor
  • DApp的未来发展趋势是什么?
  • solidity从入门到精通 第四章:智能合约的生命周期
  • 糖尿病数据分析:血压与年龄关系可视化
  • 二重循环之练习输入行数,打印等腰三角形
  • 同一个端口无法同时配置基于 server_name 的 HTTP(非加密)和 HTTPS(加密)
  • 【矩阵专题】Leetcode73.矩阵置零
  • 西门子 S7-1500分布式 I/O通信 :PROFINET IO 与 PROFIBUS DP详解(下)
  • 9、STM32的启动过程
  • Ubuntu系统下FFmpeg源码编译安装
  • 面试150 建立四叉树