rush_sync_server/proxy/
handler.rs

1use crate::proxy::ProxyManager;
2use hyper::service::{make_service_fn, service_fn};
3use hyper::{Body, Client, Request, Response, Server, Uri};
4use std::convert::Infallible;
5use std::sync::Arc;
6
7pub struct ProxyServer {
8    manager: Arc<ProxyManager>,
9}
10
11impl ProxyServer {
12    pub fn new(manager: Arc<ProxyManager>) -> Self {
13        Self { manager }
14    }
15
16    pub async fn start(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
17        let config = self.manager.get_config();
18        let addr = ([127, 0, 0, 1], config.port).into();
19
20        let manager = Arc::clone(&self.manager);
21
22        let make_svc = make_service_fn(move |_conn| {
23            let manager = Arc::clone(&manager);
24            let client = Client::new();
25
26            async move {
27                Ok::<_, Infallible>(service_fn(move |req| {
28                    let manager = Arc::clone(&manager);
29                    let client = client.clone();
30                    handle_proxy_request(req, manager, client)
31                }))
32            }
33        });
34
35        let server = Server::bind(&addr).serve(make_svc);
36
37        log::info!(
38            "Reverse Proxy listening on http://127.0.0.1:{}",
39            config.port
40        );
41        log::info!("Route pattern: {{servername}}.localhost -> 127.0.0.1:{{port}}");
42
43        if let Err(e) = server.await {
44            log::error!("Proxy server error: {}", e);
45        }
46
47        Ok(())
48    }
49
50    // NEU: HTTPS-Server hinzufügen
51    pub async fn start_with_https(&self) -> crate::core::prelude::Result<()> {
52        let config = self.manager.get_config();
53        let https_port = config.port + config.https_port_offset;
54
55        // Manager EINMAL clonen für beide Tasks
56        let manager_for_http = Arc::clone(&self.manager);
57        let manager_for_https = Arc::clone(&self.manager);
58        let config_clone = config.clone(); // Config für HTTPS Task
59
60        log::info!("Starting HTTP + HTTPS proxy servers...");
61        log::info!("  HTTP:  http://127.0.0.1:{}", config.port);
62        log::info!("  HTTPS: https://127.0.0.1:{}", https_port);
63
64        // HTTP Server Task
65        let http_task = tokio::spawn(async move {
66            let proxy_server = ProxyServer::new(manager_for_http);
67            if let Err(e) = proxy_server.start().await {
68                log::error!("HTTP proxy failed: {}", e);
69            }
70        });
71
72        // HTTPS Server Task
73        let https_task = tokio::spawn(async move {
74            // TLS-Setup
75            let tls_manager = match crate::server::tls::TlsManager::new(".rss/certs", 365) {
76                Ok(manager) => manager,
77                Err(e) => {
78                    log::error!("TLS manager creation failed: {}", e);
79                    return;
80                }
81            };
82
83            let tls_config = match tls_manager.get_rustls_config("proxy", config_clone.port) {
84                Ok(config) => config,
85                Err(e) => {
86                    log::error!("TLS config failed: {}", e);
87                    return;
88                }
89            };
90
91            // HTTPS Listener
92            let listener = match tokio::net::TcpListener::bind(("127.0.0.1", https_port)).await {
93                Ok(listener) => listener,
94                Err(e) => {
95                    log::error!("HTTPS bind failed: {}", e);
96                    return;
97                }
98            };
99
100            let acceptor = tokio_rustls::TlsAcceptor::from(tls_config);
101            log::info!("HTTPS proxy listening on https://127.0.0.1:{}", https_port);
102
103            loop {
104                let (stream, _) = match listener.accept().await {
105                    Ok(conn) => conn,
106                    Err(e) => {
107                        log::warn!("HTTPS accept failed: {}", e);
108                        continue;
109                    }
110                };
111
112                let acceptor = acceptor.clone();
113                let manager = Arc::clone(&manager_for_https);
114
115                tokio::spawn(async move {
116                    let tls_stream = match acceptor.accept(stream).await {
117                        Ok(stream) => stream,
118                        Err(e) => {
119                            log::debug!("TLS handshake failed: {}", e);
120                            return;
121                        }
122                    };
123
124                    let service = hyper::service::service_fn(move |req| {
125                        let manager = Arc::clone(&manager);
126                        let client = hyper::Client::new();
127                        handle_proxy_request(req, manager, client)
128                    });
129
130                    if let Err(e) = hyper::server::conn::Http::new()
131                        .serve_connection(tls_stream, service)
132                        .await
133                    {
134                        log::debug!("HTTPS connection error: {}", e);
135                    }
136                });
137            }
138        });
139
140        // Tasks parallel laufen lassen
141        tokio::select! {
142            _ = http_task => log::error!("HTTP task ended"),
143            _ = https_task => log::error!("HTTPS task ended"),
144        }
145
146        Ok(())
147    }
148}
149
150// Rest der handle_proxy_request Funktion bleibt gleich...
151pub async fn handle_proxy_request(
152    req: Request<Body>,
153    manager: Arc<ProxyManager>,
154    client: Client<hyper::client::HttpConnector>,
155) -> Result<Response<Body>, hyper::Error> {
156    let host = req
157        .headers()
158        .get("host")
159        .and_then(|h| h.to_str().ok())
160        .map(|s| s.to_string())
161        .unwrap_or_else(|| "localhost".to_string());
162
163    let subdomain = if let Some(dot_pos) = host.find('.') {
164        host[..dot_pos].to_string()
165    } else {
166        host.clone()
167    };
168
169    // DEBUG: Log every request
170    log::info!(
171        "Proxy Request: Host='{}' -> Subdomain='{}'",
172        host,
173        subdomain
174    );
175
176    let path_and_query = req
177        .uri()
178        .path_and_query()
179        .map(|pq| pq.as_str())
180        .unwrap_or("/")
181        .to_string();
182
183    // DEBUG: Check if route exists
184    let routes = manager.get_routes().await;
185    log::info!(
186        "Available routes: {:?}",
187        routes.iter().map(|r| &r.subdomain).collect::<Vec<_>>()
188    );
189
190    if let Some(target_port) = manager.get_target_port(&subdomain).await {
191        let target_uri = format!("http://127.0.0.1:{}{}", target_port, path_and_query);
192
193        match target_uri.parse::<Uri>() {
194            Ok(uri) => {
195                let (mut parts, body) = req.into_parts();
196                parts.uri = uri;
197                parts.headers.insert(
198                    "host",
199                    format!("127.0.0.1:{}", target_port).parse().unwrap(),
200                );
201                let backend_req = Request::from_parts(parts, body);
202
203                match client.request(backend_req).await {
204                    Ok(response) => Ok(response),
205                    Err(e) => {
206                        log::warn!("Backend request failed for {}.localhost: {}", subdomain, e);
207                        Ok(Response::builder()
208                            .status(502)
209                            .header("content-type", "text/html")
210                            .body(Body::from(format!(
211                                r#"<!DOCTYPE html>
212<html><head><title>Backend Unavailable</title></head>
213<body>
214<h1>502 Bad Gateway</h1>
215<p>Backend server for <strong>{}.localhost</strong> is not responding.</p>
216<p>Target: 127.0.0.1:{}</p>
217</body></html>"#,
218                                subdomain, target_port
219                            )))
220                            .unwrap())
221                    }
222                }
223            }
224            Err(_) => Ok(Response::builder()
225                .status(400)
226                .body(Body::from("Invalid target URI"))
227                .unwrap()),
228        }
229    } else {
230        let routes = manager.get_routes().await;
231        let route_list = routes
232            .iter()
233            .map(|route| {
234                format!(
235                    r#"<li><a href="http://{}.localhost:{}/">{}.localhost</a> → 127.0.0.1:{}</li>"#,
236                    route.subdomain,
237                    manager.get_config().port,
238                    route.subdomain,
239                    route.target_port
240                )
241            })
242            .collect::<Vec<_>>()
243            .join("\n");
244
245        Ok(Response::builder()
246            .status(404)
247            .header("content-type", "text/html")
248            .body(Body::from(format!(
249                r#"<!DOCTYPE html>
250<html>
251<head>
252    <title>Subdomain Not Found</title>
253    <style>
254        body {{ font-family: -apple-system, BlinkMacSystemFont, sans-serif; margin: 40px; }}
255        .container {{ max-width: 600px; margin: 0 auto; }}
256        h1 {{ color: #ff4757; }}
257        .routes {{ background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; }}
258        ul {{ list-style: none; padding: 0; }}
259        li {{ margin: 8px 0; }}
260        a {{ color: #0066cc; text-decoration: none; }}
261        a:hover {{ text-decoration: underline; }}
262        code {{ background: #e9ecef; padding: 2px 6px; border-radius: 4px; }}
263    </style>
264</head>
265<body>
266    <div class="container">
267        <h1>Subdomain '{}.localhost' Not Found</h1>
268        <p>The requested subdomain <code>{}.localhost</code> is not configured in the reverse proxy.</p>
269
270        <div class="routes">
271            <h3>Available Routes:</h3>
272            {}
273        </div>
274
275        <p><strong>How to add a new route:</strong></p>
276        <pre><code>cargo run server create myapp --port 8080</code></pre>
277        <p>This will automatically register <code>myapp.localhost</code> with the proxy.</p>
278    </div>
279</body>
280</html>"#, subdomain, subdomain,
281    if routes.is_empty() {
282        "<p><em>No routes configured yet. Start a server to see routes here.</em></p>".to_string()
283    } else {
284        format!("<ul>{}</ul>", route_list)
285    })))
286            .unwrap())
287    }
288}