mockforge_tunnel/
client.rs1use crate::{manager::TunnelManager, Result};
4use axum::{body::Body, extract::Request, response::Response};
5use std::sync::Arc;
6
7pub struct TunnelClient {
9 #[allow(dead_code)] manager: Arc<TunnelManager>,
11 local_url: String,
12}
13
14impl TunnelClient {
15 pub fn new(manager: Arc<TunnelManager>, local_url: impl Into<String>) -> Self {
17 Self {
18 manager,
19 local_url: local_url.into(),
20 }
21 }
22
23 pub async fn forward_request(&self, request: Request) -> Result<Response> {
25 let uri = request.uri().clone();
26 let method = request.method().clone();
27 let headers = request.headers().clone();
28
29 let local_uri = format!(
31 "{}{}",
32 self.local_url,
33 uri.path_and_query().map(|pq| pq.as_str()).unwrap_or("")
34 );
35
36 let mut local_request = reqwest::Client::new().request(method, &local_uri);
38
39 for (name, value) in headers.iter() {
41 let name_str = name.as_str();
42 if !matches!(
43 name_str,
44 "connection"
45 | "keep-alive"
46 | "proxy-authenticate"
47 | "proxy-authorization"
48 | "te"
49 | "trailer"
50 | "transfer-encoding"
51 | "upgrade"
52 ) {
53 if let Ok(value_str) = value.to_str() {
54 local_request = local_request.header(name_str, value_str);
55 }
56 }
57 }
58
59 if let Ok(host) = url::Url::parse(&self.local_url) {
61 if let Some(host_str) = host.host_str() {
62 local_request = local_request.header("Host", host_str);
63 }
64 }
65
66 let body_bytes =
68 axum::body::to_bytes(request.into_body(), usize::MAX).await.map_err(|e| {
69 crate::TunnelError::Io(std::io::Error::other(format!(
70 "Failed to read request body: {}",
71 e
72 )))
73 })?;
74
75 if !body_bytes.is_empty() {
76 local_request = local_request.body(body_bytes.to_vec());
77 }
78
79 let response = local_request
81 .send()
82 .await
83 .map_err(|e| crate::TunnelError::ConnectionFailed(e.to_string()))?;
84
85 let status = response.status();
87 let headers = response.headers().clone();
88 let body = response.bytes().await.map_err(|e| {
89 crate::TunnelError::ConnectionFailed(format!("Failed to read response: {}", e))
90 })?;
91
92 let mut response_builder = Response::builder().status(status);
93
94 for (name, value) in headers.iter() {
96 if let Ok(value_str) = value.to_str() {
97 response_builder = response_builder.header(name.as_str(), value_str);
98 }
99 }
100
101 response_builder.body(Body::from(body.to_vec())).map_err(|e| {
102 crate::TunnelError::ConnectionFailed(format!("Failed to build response: {}", e))
103 })
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use crate::config::TunnelConfig;
111 use std::sync::Arc;
112
113 fn create_test_manager() -> Arc<TunnelManager> {
114 let config = TunnelConfig {
115 provider: crate::config::TunnelProvider::SelfHosted,
116 server_url: Some("https://tunnel.example.com".to_string()),
117 auth_token: Some("test-token".to_string()),
118 subdomain: Some("test".to_string()),
119 local_url: "http://localhost:3000".to_string(),
120 protocol: "http".to_string(),
121 region: None,
122 custom_domain: None,
123 websocket_enabled: true,
124 http2_enabled: true,
125 };
126
127 Arc::new(TunnelManager::new(&config).unwrap())
128 }
129
130 #[test]
131 fn test_tunnel_client_new() {
132 let manager = create_test_manager();
133 let client = TunnelClient::new(manager.clone(), "http://localhost:3000");
134
135 assert_eq!(client.local_url, "http://localhost:3000");
136 }
137
138 #[test]
139 fn test_tunnel_client_new_with_string() {
140 let manager = create_test_manager();
141 let url = String::from("http://127.0.0.1:8080");
142 let client = TunnelClient::new(manager.clone(), url.clone());
143
144 assert_eq!(client.local_url, url);
145 }
146
147 #[test]
148 fn test_tunnel_client_new_with_different_urls() {
149 let manager = create_test_manager();
150
151 let urls = vec![
152 "http://localhost:3000",
153 "http://127.0.0.1:8080",
154 "http://0.0.0.0:5000",
155 "https://internal-api:443",
156 "http://[::1]:9000",
157 ];
158
159 for url in urls {
160 let client = TunnelClient::new(manager.clone(), url);
161 assert_eq!(client.local_url, url, "URL mismatch for {}", url);
162 }
163 }
164
165 #[test]
166 fn test_tunnel_client_new_with_https() {
167 let manager = create_test_manager();
168 let client = TunnelClient::new(manager.clone(), "https://localhost:8443");
169
170 assert_eq!(client.local_url, "https://localhost:8443");
171 }
172
173 #[test]
174 fn test_tunnel_client_new_with_custom_port() {
175 let manager = create_test_manager();
176 let client = TunnelClient::new(manager.clone(), "http://localhost:4040");
177
178 assert_eq!(client.local_url, "http://localhost:4040");
179 }
180
181 #[test]
182 fn test_tunnel_client_local_url_formatting() {
183 let manager = create_test_manager();
184
185 let test_cases = vec![
187 ("http://localhost:3000", "http://localhost:3000"),
188 ("http://localhost:3000/", "http://localhost:3000/"),
189 ("http://api.local:8080", "http://api.local:8080"),
190 ];
191
192 for (input, expected) in test_cases {
193 let client = TunnelClient::new(manager.clone(), input);
194 assert_eq!(client.local_url, expected);
195 }
196 }
197
198 #[test]
199 fn test_tunnel_client_manager_reference() {
200 let manager = create_test_manager();
201 let manager_clone = manager.clone();
202
203 let _client = TunnelClient::new(manager, "http://localhost:3000");
204
205 assert!(Arc::strong_count(&manager_clone) >= 1);
207 }
208
209 #[test]
210 fn test_tunnel_client_creation_multiple() {
211 let manager = create_test_manager();
212
213 let client1 = TunnelClient::new(manager.clone(), "http://localhost:3000");
214 let client2 = TunnelClient::new(manager.clone(), "http://localhost:4000");
215
216 assert_eq!(client1.local_url, "http://localhost:3000");
217 assert_eq!(client2.local_url, "http://localhost:4000");
218 }
219
220 #[test]
221 fn test_tunnel_client_into_conversion() {
222 let manager = create_test_manager();
223
224 let url_str = "http://localhost:3000";
226 let url_string = String::from("http://localhost:3000");
227
228 let client1 = TunnelClient::new(manager.clone(), url_str);
229 let client2 = TunnelClient::new(manager.clone(), url_string);
230
231 assert_eq!(client1.local_url, client2.local_url);
232 }
233
234 #[test]
235 fn test_tunnel_client_with_ipv6() {
236 let manager = create_test_manager();
237 let client = TunnelClient::new(manager, "http://[::1]:3000");
238
239 assert_eq!(client.local_url, "http://[::1]:3000");
240 }
241
242 #[test]
243 fn test_tunnel_client_with_hostname() {
244 let manager = create_test_manager();
245 let client = TunnelClient::new(manager, "http://backend-service:8080");
246
247 assert_eq!(client.local_url, "http://backend-service:8080");
248 }
249
250 #[test]
251 fn test_tunnel_client_url_without_port() {
252 let manager = create_test_manager();
253
254 let client_http = TunnelClient::new(manager.clone(), "http://localhost");
256 assert_eq!(client_http.local_url, "http://localhost");
257
258 let client_https = TunnelClient::new(manager.clone(), "https://localhost");
260 assert_eq!(client_https.local_url, "https://localhost");
261 }
262
263 #[test]
264 fn test_tunnel_client_empty_url() {
265 let manager = create_test_manager();
266 let client = TunnelClient::new(manager, "");
267
268 assert_eq!(client.local_url, "");
269 }
270
271 #[test]
272 fn test_tunnel_client_with_path() {
273 let manager = create_test_manager();
274 let client = TunnelClient::new(manager, "http://localhost:3000/api");
275
276 assert_eq!(client.local_url, "http://localhost:3000/api");
278 }
279
280 #[test]
281 fn test_tunnel_client_with_query_params() {
282 let manager = create_test_manager();
283 let client = TunnelClient::new(manager, "http://localhost:3000?debug=true");
284
285 assert_eq!(client.local_url, "http://localhost:3000?debug=true");
286 }
287}