首页
/ Hoppyscotsch Desktop 的 HTTP 执行引擎:tauri-plugin-relay 插件设计与使用详解

Hoppyscotsch Desktop 的 HTTP 执行引擎:tauri-plugin-relay 插件设计与使用详解

2026-09-03 17:56:38作者:管翌锬

本文围绕 Hoppyscotsch 桌面版内嵌的 Tauri 插件 tauri-plugin-relay 展开,讲清这个“HTTP 请求-响应中转”插件的能力边界(自定义头、证书、代理、多认证方式、内容类型、异步取消)、在 Tauri 2.0 应用中的安装与初始化方式,以及它从前端 JS 调用到 Rust 底层执行的完整链路。读完后,你能独立为 Tauri 应用接入一个基于 libcurl 的原生 HTTP 客户端,并理解 Hoppyscotsch 桌面端请求执行与取消的底层机制。

插件定位:为什么 Tauri 应用需要 HTTP 中继

浏览器/WebView 里的 fetch 受 CORS、证书策略与代理能力限制。tauri-plugin-relay 是一个面向 Tauri 应用的 HTTP request-response relay 插件,把真实网络请求下沉到原生 Rust 侧执行,提供 README 中列出的核心能力:

  • HTTP client 构建在 libcurl 之上;
  • SSL/TLS 证书管理(客户端证书、自定义 CA);
  • 代理支持(含代理认证);
  • 多种认证方式(Basic、Bearer、Digest);
  • 多种请求体内容处理(文本、JSON、表单、二进制);
  • 异步请求执行与取消(cancellation)支持。

在 Hoppyscotsch 仓库中,该插件以插件工作区成员的形式存放在 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-relay/。桌面端主工程通过 git 依赖锁定到指定 rev 引入它,见 src-tauri/Cargo.toml

tauri-plugin-relay = { git = "https://github.com/CuriousCorrelation/tauri-plugin-relay", rev = "273488c8f50a22ee707af6b50ccd5570851f8bc9" }

并在应用构建链中注册插件,见 src-tauri/src/lib.rs

.plugin(tauri_plugin_relay::init())

这与插件 README 中给出的 Quick Start 完全一致,可以确认桌面端确实以“插件初始化 + 前端 invoke”的模式消费该中继能力。

安装与依赖声明

该插件要求 Tauri 2.0 或更高版本。

README 的说明,插件支持直接从 GitHub 以 git 依赖方式安装。Rust 侧:

[dependencies]
tauri-plugin-relay = { git = "https://github.com/CuriousCorrelation/tauri-plugin-relay" }

前端 JS/TS 侧:

"dependencies": {
  "@CuriousCorrelation/plugin-relay": "github:CuriousCorrelation/tauri-plugin-relay"
}

插件自身的 Cargo.toml 声明了关键依赖与版本下限:tauri = "2.1.0"serdethiserror = "2"tracing,以及真正干活的核心 crate relay(同为 git 依赖),rust-version = "1.77.2"。也就是说,插件本体只是一层薄薄的 Tauri 适配层,真正的 HTTP 执行逻辑在独立的 relay crate 中。仓库内另有一个 packages/hoppscotch-relay Rust 工作区包,属于同一套中继基础设施,可作为深入阅读底层实现的入口。

前端包的 package.json 名为 @CuriousCorrelation/plugin-relay,产物在 dist-js/(同时提供 ESM index.js 与 CJS index.cjs,类型定义在 index.d.ts),依赖 @tauri-apps/api 2.1.1,构建脚本为 tsc && rollup -c

插件初始化:init() 到底做了什么

Rust 侧的入口是 src/lib.rs 中的 init()

pub fn init<R: Runtime>() -> TauriPlugin<R> {
    Builder::new("relay")
        .invoke_handler(tauri::generate_handler![
            commands::execute,
            commands::cancel
        ])
        .setup(|app, api| {
            #[cfg(mobile)]
            { let relay = mobile::init(app, api)?; app.manage(relay); }

            #[cfg(desktop)]
            { let relay = desktop::init(app, api)?; app.manage(relay); }
            Ok(())
        })
        .build()
}

从源码结构看,init() 做了三件事:

  1. 注册命令:通过 generate_handler! 暴露且仅暴露两个可被前端调用的命令——executecancel,对应 src/commands.rs
  2. 平台分支:按 #[cfg(desktop)] / #[cfg(mobile)] 条件编译分别初始化 desktop.rsmobile.rs 中的 Relay 实现,并 app.manage(relay) 存入 Tauri 状态;
  3. 提供状态访问器RelayExt trait(lib.rs)让任何实现了 Manager 的对象都能通过 app.relay() 取出 Relay 状态实例。

desktop 平台的 Relay 实现(src/desktop.rs)本身很薄:

pub struct Relay<R: Runtime>(AppHandle<R>);

impl<R: Runtime> Relay<R> {
    pub async fn execute(&self, request: RunRequest) -> Result<ExecuteResponse> {
        match relay::execute(request).await {
            Ok(response) => Ok(ExecuteResponse::Success { response }),
            Err(error)   => Ok(ExecuteResponse::Error { error }),
        }
    }

    pub async fn cancel(&self, request_id: CancelRequest) -> Result<CancelResponse> {
        if let Err(e) = relay::cancel(request_id).await {
            return Err(e.into());
        }
        Ok(())
    }
}

可以看到执行路径为:前端 invoke → Tauri 命令 → Relay::executerelay::execute(request)(relay crate 真正发起 libcurl 请求)。一个值得注意的设计是:HTTP 执行失败(网络错误、证书错误等)不会让命令返回 Err,而是包装成 ExecuteResponse::Error 正常返回;只有取消失败这类“插件自身故障”才会走 Err 分支。

前端调用:execute 与 cancel

插件的前端 API 只有两个函数,定义在 guest-js/index.ts

export async function execute(request: Request): Promise<RequestResult> {
  return await invoke<RequestResult>('plugin:relay|execute', { request })
}

export async function cancel(requestId: number): Promise<void> {
  return await invoke<void>('plugin:relay|cancel', { requestId })
}

即通过 Tauri 的命令通道 plugin:relay|execute / plugin:relay|cancel 与 Rust 侧通信。README 给出的最简示例:

import { execute, cancel } from '@CuriousCorrelation/plugin-relay'

// Execute a request
const result = await execute({
  id: 1,
  url: "https://api.example.com/data",
  method: "POST",
  headers: {
    "Content-Type": ["application/json"]
  },
  content: {
    kind: "json",
    content: { hello: "world" }
  }
})

// Cancel a request
await cancel(1)

request.id 是请求的唯一标识:execute 传入的 id 会随响应原样带回,cancel(id) 则凭此 id 中断仍在飞行中的请求。返回的 RequestResult 是一个带 kind 标签的联合类型:

export type RequestResult =
  | { kind: 'success'; response: Response }
  | { kind: 'error'; error: RelayError }

与 Rust 侧 src/models.rsExecuteResponse#[serde(tag = "kind")],序列化为 "success" / "error")一一对应。

请求类型系统:Method、Version、Content 与 Auth

guest-js/index.ts 定义了完整的请求类型体系,这是插件最“干货”的部分。

方法与版本Method 覆盖 GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS/CONNECT/TRACE 九个值;Version 支持 HTTP/1.0HTTP/1.1HTTP/2.0HTTP/3.0,说明底层中继在 libcurl 之上可协商现代协议。

内容类型:README 的 Content Types 表格列出了五类请求体:

类型 说明
text 纯文本内容
json JSON 数据,自动解析
form 支持文件上传的 multipart 表单数据
binary 原始二进制数据,可带 MIME 类型
urlencoded URL 编码表单数据

guest-js/index.tsContentType 联合类型比表格更丰富,还包含 xmlmultipartstream

export type ContentType =
    | { kind: "text"; content: string; mediaType: ... }
    | { kind: "json"; content: unknown; mediaType: MediaType.APPLICATION_JSON | MediaType.APPLICATION_LD_JSON }
    | { kind: "xml"; content: string; mediaType: ... }
    | { kind: "form"; content: FormData; mediaType: MediaType.APPLICATION_FORM }
    | { kind: "binary"; content: Uint8Array; mediaType: ...; filename?: string }
    | { kind: "multipart"; content: FormData; mediaType: MediaType.MULTIPART_FORM }
    | { kind: "urlencoded"; content: string; mediaType: ... }
    | { kind: "stream"; content: ReadableStream; mediaType: string }

其中 FormData 的值为 [string, FormDataValue[]][]FormDataValue 支持 textfile(带 filenamecontentTypeUint8Array 数据),因此 multipart 上传是完整建模的。

认证方式:README 的 Authentication 表格列出三种:

方式 说明
basic Basic HTTP 认证
bearer Bearer Token 认证
digest Digest 认证(MD5、SHA-256、SHA-512)

AuthType 类型(guest-js/index.ts)还进一步扩展了 oauth2(authorization_code / client_credentials / password / implicit 四种 grant,可携带 accessToken / refreshToken)、apikey(header 或 query 注入)与 aws(SigV4 风格的 accessKey/secretKey/region/service 签名,可加 sessionToken)等变体,与 Hoppyscotsch 主产品的认证能力面保持一致。

完整 Request 结构

export interface Request {
  id: number
  url: string
  method: Method
  version: Version
  headers?: Record<string, string>
  params?: Record<string, string>
  content?: ContentType
  auth?: AuthType

  security?: {
    certificates?: {
      client?: CertificateType
      ca?: Array<Uint8Array>
    }
    verifyHost?: boolean
    verifyPeer?: boolean
  }

  proxy?: {
    url: string
    auth?: { username: string; password: string }
  }

  meta?: RequestMeta
}

RequestOptions(经 meta.options 传入)提供请求级行为开关:timeoutfollowRedirectsmaxRedirectsdecompresscookieskeepAliveguest-js/index.ts)。

安全与代理配置

README 的 Security 一节指出插件提供四项安全能力:客户端证书支持(PEM、PKCS#12)、自定义 CA 证书、证书校验控制、主机验证设置。对应到类型定义:

export type CertificateType =
  | { kind: "pem"; cert: Uint8Array; key: Uint8Array }
  | { kind: "pfx"; data: Uint8Array; password: string }

security.certificates.client 接受 PEM(证书 + 私钥各一段字节)或 PFX/PKCS#12(整包字节 + 密码)两种形态;security.certificates.ca 是 CA 证书字节数组,用于私有 PKI 场景的信任锚扩展;verifyHost / verifyPeer 分别控制主机名验证与证书链验证,用于自签名证书等开发/内网调试场景。代理则通过 proxy.url 指定,可选 proxy.auth 提供代理端 Basic 认证。

响应结构与错误处理

成功时前端拿到的 Responseguest-js/index.ts)包含:状态码(StatusCode 联合类型穷举了 1xx~5xx 常用状态码)、statusText、HTTP 版本、响应头、可选的 cookie 列表(含 domainpathexpiressecurehttpOnlysameSite)、bodyUint8Array + mediaType),以及 meta 元数据:

meta: {
  timing: { start: number; end: number },
  size: { headers: number; body: number; total: number }
}

也就是说,计时与体积统计(头/体/总字节)由中继层直接产出,前端无需自行掐表,这对 Hoppyscotsch 界面里的响应耗时展示来说是关键支撑。

失败时的 RelayError 是五类加一类 unsupported 的标签联合:

export type RelayError =
  | UnsupportedFeatureError
  | { kind: "network"; message: string; cause?: unknown }
  | { kind: "timeout"; message: string; phase?: "connect" | "tls" | "response" }
  | { kind: "certificate"; message: string; cause?: unknown }
  | { kind: "parse"; message: string; cause?: unknown }
  | { kind: "abort"; message: string }

这与 README Error Handling 一节列出的“网络失败、证书问题、超时场景、解析错误、请求取消”五类详细错误信息逐条对应;timeout 还细分了 connect / tls / response 三个阶段,便于定位是连接慢、TLS 握手慢还是等响应体慢。unsupported_feature 则用于某特性(如 stream 内容)在当前平台中继实现中不可用的情况。

Rust 侧的顶层 Error 枚举定义在 src/error.rsio::Error 透传、relay::error::RelayError 透传(thiserror::Error 派生),移动端还有 PluginInvokeError。其自定义 Serialize 实现把错误序列化为字符串传给前端。而 ExecuteResponse::Error { error } 路径则会把 RelayError 结构化地序列化给前端,两者配合覆盖了“插件级故障”与“请求级失败”两个层次。

权限声明

Tauri 2.0 插件通过 TOML 声明权限。本插件的默认权限组在 permissions/default.toml

[default]
description = "Default permissions for the plugin"
permissions = ["allow-execute", "allow-cancel"]

即默认放行 executecancel 两个命令;permissions/autogenerated/ 目录下还有按命令粒度自动生成的 execute.tomlcancel.tomlschema.json,宿主应用可在 capabilities 中按需收紧。

开发环境与相关 crate

按 README Development 一节,本地开发该插件需要:Rust 1.77.2+(与 Cargo.tomlrust-version 一致)、Node.js 18+、pnpm、以及带 SSL 支持的 libcurl。插件目录内还配有 devenv.nix / devenv.yaml,可在 Nix devenv 环境中获取一致的构建依赖。

值得对照阅读的相关代码:

  • src/commands.rs:两个 Tauri 命令入口,含 tracing 日志埋点;
  • src/desktop.rs:桌面平台 Relayrelay crate 的转发逻辑;
  • guest-js/index.ts:前端完整类型定义(方法与状态码的语义注释、内容/认证/证书/代理的全部形态);
  • packages/hoppscotch-relay/src/:仓库内的 relay 工作区包,展示同一套中继机制在 Rust 侧的组成(relay.rsinterop.rserror.rs)。

小结

tauri-plugin-relay 是 Hoppyscotsch 桌面端请求执行的原生层:Tauri 命令通道只做转发(execute / cancel),libcurl 承担网络能力,而类型系统在前端(guest-js/index.ts)与 Rust(relay crate 的 RelayRequest/RelayResponse)之间保持 serde 双端对齐。理解 README 中的能力清单,再对照 lib.rs 的插件装配与 desktop.rs 的转发实现,就能完整把握“前端一条 execute 调用如何变成一次可控、可取消、可携带证书与代理配置的原生 HTTP 请求”这一完整链路。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384