use std::sync::Arc;
use std::net::SocketAddr;
use std::convert::Infallible;
use tokio::sync::RwLock;
use hyper::body::Incoming;
use hyper::{Request, Response};
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper_util::rt::TokioIo;
use bytes::Bytes;
use http_body_util::{BodyExt, Full};
use serde::{Serialize, Deserialize};
use crate::core::{ProxyCore, ProxyRequest, ProxyResponse, ProxyError, HttpMethod, RequestContext};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
#[serde(default = "default_host")]
pub host: String,
#[serde(default = "default_port")]
pub port: u16,
}
fn default_host() -> String {
"127.0.0.1".to_string()
}
fn default_port() -> u16 {
8080
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
host: default_host(),
port: default_port(),
}
}
}
#[derive(Debug, Clone)]
pub struct ProxyServer {
config: ServerConfig,
core: Arc<ProxyCore>,
}
impl ProxyServer {
pub fn new(config: ServerConfig, core: Arc<ProxyCore>) -> Self {
Self { config, core }
}
pub async fn start(&self) -> Result<(), ProxyError> {
let addr: SocketAddr = format!("{}:{}", self.config.host, self.config.port)
.parse()
.map_err(|e| ProxyError::Other(format!("Invalid server address: {}", e)))?;
let core = self.core.clone();
let listener = tokio::net::TcpListener::bind(addr).await
.map_err(|e| ProxyError::Other(format!("Failed to bind to address: {}", e)))?;
log::info!("Foxy proxy server listening on http://{}", addr);
loop {
let (stream, remote_addr) = match listener.accept().await {
Ok(conn) => conn,
Err(e) => {
log::error!("Failed to accept connection: {}", e);
continue;
}
};
let core = core.clone();
let client_ip = remote_addr.ip().to_string();
tokio::spawn(async move {
let service = service_fn(move |req| {
let core = core.clone();
let client_ip = client_ip.clone();
handle_request(req, core, client_ip)
});
let io = TokioIo::new(stream);
if let Err(e) = http1::Builder::new()
.serve_connection(io, service)
.await {
log::error!("Error serving connection: {}", e);
}
});
}
}
}
async fn convert_hyper_request(req: Request<Incoming>, client_ip: String) -> Result<ProxyRequest, ProxyError> {
let method = HttpMethod::from(req.method());
let uri = req.uri();
let path = uri.path().to_string();
let query = uri.query().map(|q| q.to_string());
let headers = req.headers().clone();
let body_bytes = req.into_body().collect()
.await
.map_err(|e| ProxyError::Other(format!("Failed to read request body: {}", e)))?
.to_bytes()
.to_vec();
let context = Arc::new(RwLock::new(RequestContext {
client_ip: Some(client_ip),
start_time: Some(std::time::Instant::now()),
attributes: std::collections::HashMap::new(),
}));
Ok(ProxyRequest {
method,
path,
query,
headers,
body: body_bytes,
context,
})
}
fn convert_proxy_response(resp: ProxyResponse) -> Result<Response<Full<Bytes>>, ProxyError> {
let mut builder = Response::builder()
.status(resp.status);
let headers = builder.headers_mut().ok_or_else(||
ProxyError::Other("Failed to get response headers".to_string())
)?;
for (name, value) in resp.headers.iter() {
headers.insert(name, value.clone());
}
let response = builder
.body(Full::new(Bytes::from(resp.body)))
.map_err(|e| ProxyError::Other(format!("Failed to create response: {}", e)))?;
Ok(response)
}
async fn handle_request(
req: Request<Incoming>,
core: Arc<ProxyCore>,
client_ip: String,
) -> Result<Response<Full<Bytes>>, Infallible> {
let proxy_req = match convert_hyper_request(req, client_ip).await {
Ok(req) => req,
Err(e) => {
log::error!("Failed to convert request: {}", e);
return Ok(Response::builder()
.status(500)
.body(Full::new(Bytes::from("Internal Server Error")))
.unwrap());
}
};
match core.process_request(proxy_req).await {
Ok(proxy_resp) => {
match convert_proxy_response(proxy_resp) {
Ok(resp) => Ok(resp),
Err(e) => {
log::error!("Failed to convert response: {}", e);
Ok(Response::builder()
.status(500)
.body(Full::new(Bytes::from("Internal Server Error")))
.unwrap())
}
}
},
Err(e) => {
log::error!("Proxy error: {}", e);
let (status, message) = match e {
ProxyError::Timeout(duration) =>
(504, format!("Gateway Timeout: Request timed out after {:?}", duration)),
ProxyError::RoutingError(_) =>
(404, "Not Found: No route matched the request".to_string()),
_ =>
(500, "Internal Server Error".to_string()),
};
Ok(Response::builder()
.status(status)
.body(Full::new(Bytes::from(message)))
.unwrap())
}
}
}
#[allow(dead_code)]
fn convert_hyper_response(resp: Response<Full<Bytes>>) -> ProxyResponse {
use crate::core::ResponseContext;
let status = resp.status().as_u16();
let headers = resp.headers().clone();
let body = Vec::new();
ProxyResponse {
status,
headers,
body,
context: Arc::new(RwLock::new(ResponseContext::default())),
}
}