Skip to main content

gout_api/
client.rs

1//! `GoutClient` — 使用普通 `api-key` 与 goutd 通信。
2//!
3//! 负责隧道的 CRUD 操作(创建、列出、删除)。
4//! 数据通道的握手和转发由 [`data_channel`](crate::data_channel) 模块处理。
5
6use crate::{ApiResponse, 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        if !resp.status().is_success() {
74            let api_resp: ApiResponse<TunnelResponse> = resp
75                .json()
76                .await
77                .context("parse error response")?;
78            anyhow::bail!("server error: {}", api_resp.error.unwrap_or_default());
79        }
80
81        let api_resp: ApiResponse<TunnelResponse> = resp
82            .json()
83            .await
84            .context("parse success response")?;
85        api_resp.data.context("no tunnel data in response")
86    }
87
88    /// 列出所有活跃隧道。
89    ///
90    /// 返回当前服务端上状态为 "waiting" 或 "active" 的隧道列表。
91    /// 已关闭或已过期的隧道不会出现在列表中。
92    pub async fn list_tunnels(&self) -> Result<Vec<crate::TunnelListEntry>> {
93        let resp = self
94            .inner
95            .get(format!("{}/api/v1/tunnels", self.base))
96            .header("X-Api-Key", &self.api_key)
97            .send()
98            .await
99            .context("REST list tunnels failed")?;
100
101        let api_resp: ApiResponse<Vec<crate::TunnelListEntry>> = resp
102            .json()
103            .await
104            .context("parse list tunnels response")?;
105        Ok(api_resp.data.unwrap_or_default())
106    }
107
108    /// 删除指定 token 的隧道。
109    ///
110    /// 服务端会关闭对应的公网端口监听并清理所有相关资源。
111    pub async fn delete_tunnel(&self, token: u64) -> Result<()> {
112        let resp = self
113            .inner
114            .delete(format!("{}/api/v1/tunnels/{}", self.base, token))
115            .header("X-Api-Key", &self.api_key)
116            .send()
117            .await
118            .context("REST delete tunnel failed")?;
119
120        if !resp.status().is_success() {
121            anyhow::bail!("delete tunnel failed: {}", resp.status());
122        }
123        Ok(())
124    }
125
126    /// 获取服务端地址(不含 `http://` 前缀)。
127    pub fn server_addr(&self) -> &str {
128        &self.base[7..]
129    }
130
131    /// 获取当前客户端使用的 API key。
132    pub fn api_key(&self) -> &str {
133        &self.api_key
134    }
135}