1#[cfg(test)]
22mod tests;
23mod health;
24
25use std::sync::Arc;
26use std::net::SocketAddr;
27use std::convert::Infallible;
28use tokio::sync::RwLock;
29use hyper::body::Incoming;
30use hyper::{Request, Response};
31use hyper_util::server::conn::auto::Builder as AutoBuilder;
32use hyper_util::rt::TokioExecutor;
33use hyper::service::service_fn;
34use hyper_util::rt::TokioIo;
35use bytes::Bytes;
36use futures_util::TryStreamExt;
37use http_body_util::{BodyExt, Full};
38use reqwest::Body;
39use serde::{Serialize, Deserialize};
40use log::{debug, info, warn, error, trace};
41use tokio::signal;
42use tokio::task::{Id, JoinSet};
43use crate::core::{ProxyCore, ProxyRequest, ProxyResponse, ProxyError, HttpMethod, RequestContext};
44use std::collections::HashMap;
45use tokio::sync::oneshot;
46use health::HealthServer;
47
48#[cfg(unix)]
49use tokio::signal::unix::{signal, SignalKind};
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct ServerConfig {
54 #[serde(default = "default_host")]
56 pub host: String,
57
58 #[serde(default = "default_port")]
60 pub port: u16,
61
62 #[serde(default = "default_health_port")]
64 pub health_port: u16,
65}
66
67fn default_host() -> String {
68 "127.0.0.1".to_string()
69}
70
71fn default_port() -> u16 {
72 8080
73}
74
75fn default_health_port() -> u16 {
76 8081
77}
78
79impl Default for ServerConfig {
80 fn default() -> Self {
81 Self {
82 host: default_host(),
83 port: default_port(),
84 health_port: default_health_port(),
85 }
86 }
87}
88
89#[derive(Debug, Clone)]
91pub struct ProxyServer {
92 config: ServerConfig,
94 core: Arc<ProxyCore>,
96 shutdown_senders: Arc<RwLock<HashMap<Id, oneshot::Sender<()>>>>,
98}
99
100impl ProxyServer {
101 pub fn new(config: ServerConfig, core: Arc<ProxyCore>) -> Self {
103 Self {
104 config,
105 core,
106 shutdown_senders: Arc::new(RwLock::new(HashMap::new())),
107 }
108 }
109
110 pub async fn start(&self) -> Result<(), ProxyError> {
112 let addr = format!("{}:{}", self.config.host, self.config.port)
113 .parse::<SocketAddr>()
114 .map_err(|e| ProxyError::Other(format!("Invalid server address: {}", e)))?;
115
116 let listener = tokio::net::TcpListener::bind(addr)
117 .await
118 .map_err(|e| ProxyError::Other(format!("Failed to bind: {}", e)))?;
119
120 info!("Foxy proxy listening on http://{}", addr);
121
122 let health_server = HealthServer::new(self.config.health_port);
123 health_server.set_ready();
124
125 let ctrl_c = signal::ctrl_c();
127
128 #[cfg(unix)]
130 let mut term_stream = signal(SignalKind::terminate())
131 .map_err(|e| ProxyError::Other(format!("Cannot install SIGTERM handler: {}", e)))?;
132
133 #[cfg(unix)]
135 let sigterm = term_stream.recv();
136 #[cfg(not(unix))]
137 let sigterm = std::future::pending();
138
139 tokio::pin!(ctrl_c);
141 tokio::pin!(sigterm);
142
143 let shutdown_senders = self.shutdown_senders.clone();
145
146 let mut join_set = JoinSet::new();
148 let core = self.core.clone();
149
150 let shutdown_initiated = Arc::new(std::sync::atomic::AtomicBool::new(false));
152 let shutdown_initiated_clone = shutdown_initiated.clone();
153
154 loop {
155 tokio::select! {
156 _ = &mut ctrl_c => {
157 info!("Received Ctrl-C; initiating graceful shutdown");
158 shutdown_initiated_clone.store(true, std::sync::atomic::Ordering::SeqCst);
159 break;
160 }
161 _ = &mut sigterm => {
162 info!("Received SIGTERM; initiating graceful shutdown");
163 shutdown_initiated_clone.store(true, std::sync::atomic::Ordering::SeqCst);
164 break;
165 }
166 accept = listener.accept() => {
167 match accept {
168 Ok((stream, remote_addr)) => {
169 if shutdown_initiated.load(std::sync::atomic::Ordering::SeqCst) {
171 info!("Rejecting new connection during shutdown");
172 continue;
173 }
174
175 let core = core.clone();
176 let client_ip = remote_addr.ip().to_string();
177 let (tx, rx) = oneshot::channel();
178
179 let handle = join_set.spawn(async move {
180 let service = service_fn(move |req: Request<Incoming>| {
181 debug!("Incoming over {:?}", req.version());
182 handle_request(req, core.clone(), client_ip.clone())
183 });
184 let io = TokioIo::new(stream);
185
186 let builder = {
188 let mut b = AutoBuilder::new(TokioExecutor::new());
189 b.http1();
190 b.http2();
191 b
192 };
193
194 let graceful_shutdown = async {
196 let _ = rx.await;
198 debug!("Connection received shutdown signal");
199 };
200
201 let connection = builder.serve_connection(io, service);
203
204 tokio::select! {
206 res = connection => {
207 if let Err(e) = res {
208 error!("Connection error: {}", e);
209 }
210 }
211 _ = graceful_shutdown => {
212 debug!("Connection shutting down gracefully");
213 }
214 }
215 });
216
217 shutdown_senders.write().await.insert(handle.id(), tx);
219 }
220 Err(e) => error!("Accept error: {}", e),
221 }
222 }
223 }
224 }
225
226 info!("Shutting down; waiting for {} connection(s)", join_set.len());
228
229 {
231 let mut senders = shutdown_senders.write().await;
232 for (_, sender) in senders.drain() {
233 let _ = sender.send(());
234 }
235 }
236
237 let shutdown_timeout = tokio::time::Duration::from_secs(30);
239 let shutdown_future = async {
240 while let Some(res) = join_set.join_next().await {
241 if let Err(e) = res {
242 error!("Connection task failed: {}", e);
243 }
244 }
245 };
246
247 match tokio::time::timeout(shutdown_timeout, shutdown_future).await {
248 Ok(_) => info!("All connections drained gracefully"),
249 Err(_) => warn!("Shutdown timed out after {} seconds", shutdown_timeout.as_secs()),
250 }
251
252 info!("Shutdown complete");
253 Ok(())
254 }
255}
256
257async fn convert_hyper_request(
259 req: Request<Incoming>,
260 client_ip: String,
261) -> Result<ProxyRequest, ProxyError> {
262 let method = HttpMethod::from(req.method());
263 let uri = req.uri().clone();
264 let path = uri.path().to_owned();
265 let query = uri.query().map(|q| q.to_owned());
266 let headers = req.headers().clone();
267
268 log::trace!("Converting request: {} {} with {} headers",
269 method, path, headers.len());
270
271 let hyper_stream = req.into_body().into_data_stream();
273 let byte_stream = hyper_stream.map_ok(Bytes::from);
274 let body = reqwest::Body::wrap_stream(byte_stream);
275
276 Ok(ProxyRequest {
277 method,
278 path,
279 query,
280 headers,
281 body,
282 context: Arc::new(RwLock::new(RequestContext {
283 client_ip: Some(client_ip),
284 start_time: Some(std::time::Instant::now()),
285 attributes: std::collections::HashMap::new(),
286 })),
287 })
288}
289
290fn convert_proxy_response(resp: ProxyResponse) -> Result<Response<Body>, ProxyError> {
292 log::trace!("Converting response with status {} and {} headers",
293 resp.status, resp.headers.len());
294
295 let stream = resp
296 .body
297 .into_data_stream()
298 .map_err(|e| {
299 log::error!("Error streaming response body: {}", e);
300 std::io::Error::new(std::io::ErrorKind::Other, e)
301 });
302
303 let body = Body::wrap_stream(stream);
304
305 let mut builder = Response::builder().status(resp.status);
306 match builder.headers_mut() {
307 Some(headers) => {
308 *headers = resp.headers;
309 Ok(())
310 },
311 None => {
312 log::error!("Failed to get mutable headers from response builder");
313 Err(ProxyError::Other("unable to set headers".into()))
314 }
315 }?;
316
317 builder
318 .body(body)
319 .map_err(|e| {
320 let err = ProxyError::Other(e.to_string());
321 log::error!("Failed to build response: {}", err);
322 err
323 })
324}
325
326async fn handle_request(
328 req: Request<Incoming>,
329 core: Arc<ProxyCore>,
330 client_ip: String,
331) -> Result<Response<Body>, Infallible> {
332 let method = req.method().clone();
334 let path = req.uri().path().to_owned();
335
336 log::debug!("Received request: {} {}", method, path);
337
338 let proxy_req = match convert_hyper_request(req, client_ip.clone()).await {
339 Ok(r) => r,
340 Err(e) => {
341 log::error!("Failed to convert request {} {}: {}", method, path, e);
342 return Ok(Response::builder()
343 .status(500)
344 .body(Body::from("Internal Server Error"))
345 .unwrap());
346 }
347 };
348
349 match core.process_request(proxy_req).await {
351 Ok(proxy_resp) => {
352 log::debug!("Successfully processed request {} {} -> {}", method, path, proxy_resp.status);
353 match convert_proxy_response(proxy_resp) {
354 Ok(resp) => Ok(resp),
355 Err(e) => {
356 log::error!("Failed to convert response for {} {}: {}", method, path, e);
357 Ok(Response::builder()
358 .status(500)
359 .body(Body::from("Internal Server Error"))
360 .unwrap())
361 }
362 }
363 },
364 Err(e) => {
365 let (status, msg) = match &e {
366 ProxyError::Timeout(d) => {
367 log::warn!("Request {} {} timed out after {:?}", method, path, d);
368 (504, format!("Gateway Timeout after {d:?}"))
369 },
370 ProxyError::RoutingError(msg) => {
371 log::warn!("Routing error for {} {}: {}", method, path, msg);
372 (404, "Route not found".into())
373 },
374 ProxyError::SecurityError(msg) => {
375 log::warn!("Security error for {} {}: {}", method, path, msg);
376 (403, "Forbidden".into())
377 },
378 ProxyError::ClientError(err) => {
379 log::error!("Client error for {} {}: {}", method, path, err);
380 (502, "Bad Gateway".into())
381 },
382 _ => {
383 log::error!("Internal error processing {} {}: {}", method, path, e);
384 (500, "Internal Server Error".into())
385 },
386 };
387
388 Ok(Response::builder()
389 .status(status)
390 .body(Body::from(msg))
391 .unwrap())
392 }
393 }
394}