gout_api/client.rs
1//! `GoutClient` — 使用普通 `api-key` 与 goutd 通信。
2//!
3//! 负责隧道的 CRUD 操作(创建、列出、删除)。
4//! 数据通道的握手和转发由 [`data_channel`](crate::data_channel) 模块处理。
5
6use crate::{CreateTunnelRequest, TunnelResponse};
7use anyhow::{Context, Result};
8
9/// 隧道操作客户端。
10///
11/// 使用服务端分配的普通 `api-key`(非 admin key)进行认证。
12/// 客户端实例持有 HTTP 连接池,建议复用。
13///
14/// # 示例
15///
16/// ```no_run
17/// # async fn doc() {
18/// use gout_api::client::GoutClient;
19/// use gout_api::TunnelType;
20///
21/// let gout = GoutClient::new("server.example.com:8080", "sk-your-key");
22/// let tunnel = gout.create_tunnel(TunnelType::Tcp, 4000).await.unwrap();
23/// gout.delete_tunnel(tunnel.token).await.unwrap();
24/// # }
25/// ```
26pub struct GoutClient {
27 inner: reqwest::Client,
28 base: String,
29 api_key: String,
30}
31
32impl GoutClient {
33 /// 创建一个新的 `GoutClient`。
34 ///
35 /// # 参数
36 ///
37 /// * `server_addr` — 服务端地址,`host:port` 格式,如 `"example.com:8080"`
38 /// * `api_key` — 普通隧道 API key
39 pub fn new(server_addr: &str, api_key: &str) -> Self {
40 Self {
41 inner: reqwest::Client::new(),
42 base: format!("http://{server_addr}"),
43 api_key: api_key.to_string(),
44 }
45 }
46
47 /// 创建一个新隧道。
48 ///
49 /// 返回 [`TunnelResponse`],调用方据此建立数据通道连接。
50 /// 数据通道的握手和 pipe 由 [`data_channel`](crate::data_channel) 模块提供。
51 ///
52 /// # 参数
53 ///
54 /// * `tunnel_type` — 隧道协议类型(TCP / UDP / HTTP)
55 /// * `local_port` — 本地服务端口号
56 pub async fn create_tunnel(
57 &self,
58 tunnel_type: crate::TunnelType,
59 local_port: u16,
60 ) -> Result<TunnelResponse> {
61 let resp = self
62 .inner
63 .post(format!("{}/api/v1/tunnels", self.base))
64 .header("X-Api-Key", &self.api_key)
65 .json(&CreateTunnelRequest {
66 tunnel_type,
67 local_port: Some(local_port),
68 })
69 .send()
70 .await
71 .context("REST create tunnel failed")?;
72
73 crate::parse_api_response(resp).await
74 }
75
76 /// 列出所有活跃隧道。
77 ///
78 /// 返回当前服务端上状态为 "waiting" 或 "active" 的隧道列表。
79 /// 已关闭或已过期的隧道不会出现在列表中。
80 pub async fn list_tunnels(&self) -> Result<Vec<crate::TunnelListEntry>> {
81 let resp = self
82 .inner
83 .get(format!("{}/api/v1/tunnels", self.base))
84 .header("X-Api-Key", &self.api_key)
85 .send()
86 .await
87 .context("REST list tunnels failed")?;
88
89 crate::parse_api_response(resp).await
90 }
91
92 /// 删除指定 token 的隧道。
93 ///
94 /// 服务端会关闭对应的公网端口监听并清理所有相关资源。
95 pub async fn delete_tunnel(&self, token: u64) -> Result<()> {
96 let resp = self
97 .inner
98 .delete(format!("{}/api/v1/tunnels/{}", self.base, token))
99 .header("X-Api-Key", &self.api_key)
100 .send()
101 .await
102 .context("REST delete tunnel failed")?;
103
104 if !resp.status().is_success() {
105 anyhow::bail!("delete tunnel failed: {}", resp.status());
106 }
107 Ok(())
108 }
109
110 /// 获取服务端地址(不含 `http://` 前缀)。
111 pub fn server_addr(&self) -> &str {
112 &self.base[7..]
113 }
114
115 /// 获取当前客户端使用的 API key。
116 pub fn api_key(&self) -> &str {
117 &self.api_key
118 }
119}