Skip to main content

tiny_proxy/api/
server.rs

1//! API server for proxy management
2//!
3//! This module provides a REST API for managing the proxy configuration,
4//! including viewing and updating configuration settings.
5
6use http_body_util::Full;
7use hyper::body::Incoming;
8use hyper::server::conn::http1;
9use hyper::service::service_fn;
10use hyper::{Request, Response};
11use hyper_util::rt::TokioIo;
12use std::net::SocketAddr;
13use std::sync::Arc;
14use tokio::net::TcpListener;
15use tracing::{error, info};
16
17use crate::api::endpoints;
18
19use crate::config::Config;
20use crate::error::Result;
21
22/// Start the API server for proxy management
23///
24/// This server provides REST endpoints for:
25/// - GET /config - Get current configuration
26/// - POST /config - Update configuration
27/// - GET /health - Health check endpoint
28///
29/// # Arguments
30///
31/// * `addr` - Address to listen on (e.g., "127.0.0.1:8081")
32/// * `config` - Shared configuration wrapped in Arc<RwLock<Config>>
33///
34/// # Example
35///
36/// ```no_run
37/// # use tiny_proxy::{Config, api};
38/// # use std::sync::Arc;
39/// # #[tokio::main]
40/// # async fn main() -> anyhow::Result<()> {
41/// let config = Arc::new(tokio::sync::RwLock::new(Config::from_file("config.caddy")?));
42/// api::server::start_api_server("127.0.0.1:8081", config).await?;
43/// # Ok(())
44/// # }
45/// ```
46pub async fn start_api_server(addr: &str, config: Arc<tokio::sync::RwLock<Config>>) -> Result<()> {
47    let addr: SocketAddr = addr.parse()?;
48    start_api_server_with_addr(addr, config).await
49}
50
51/// Start the API server with a parsed SocketAddr
52///
53/// This is a convenience method if you already have a parsed SocketAddr.
54///
55/// # Arguments
56///
57/// * `addr` - Parsed SocketAddr to listen on
58/// * `config` - Shared configuration wrapped in Arc<RwLock<Config>>
59pub async fn start_api_server_with_addr(
60    addr: SocketAddr,
61    config: Arc<tokio::sync::RwLock<Config>>,
62) -> Result<()> {
63    let listener = TcpListener::bind(&addr).await?;
64
65    info!("API server listening on http://{}", addr);
66
67    loop {
68        let (stream, _) = listener.accept().await?;
69        let io = TokioIo::new(stream);
70        let config = config.clone();
71
72        tokio::task::spawn(async move {
73            let service = service_fn(move |req| {
74                let config = config.clone();
75                handle_api_request(req, config)
76            });
77
78            if let Err(err) = http1::Builder::new().serve_connection(io, service).await {
79                error!("Error serving API connection: {:?}", err);
80            }
81        });
82    }
83}
84
85/// Handle incoming API requests
86///
87/// Routes requests to appropriate endpoints based on method and path.
88async fn handle_api_request(
89    req: Request<Incoming>,
90    config: Arc<tokio::sync::RwLock<Config>>,
91) -> anyhow::Result<Response<Full<bytes::Bytes>>> {
92    // TODO: Add authentication middleware if needed
93    // let req = middleware::auth_middleware(req, api_key).await?;
94
95    let path = req.uri().path();
96    let method = req.method();
97
98    info!("API request: {} {}", method, path);
99
100    match (method.as_str(), path) {
101        ("GET", "/config") => endpoints::handle_get_config(req, config).await,
102        ("POST", "/config") => endpoints::handle_post_config(req, config).await,
103        ("GET", "/health") => endpoints::handle_health_check(req).await,
104        _ => {
105            // 404 Not Found
106            let response = Response::builder()
107                .status(404)
108                .body(Full::new(bytes::Bytes::from("Not Found".to_string())))
109                .unwrap();
110            Ok(response)
111        }
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_api_server_address_parsing() {
121        let addr: SocketAddr = "127.0.0.1:8081".parse().unwrap();
122        assert_eq!(addr.port(), 8081);
123        assert_eq!(addr.ip().to_string(), "127.0.0.1");
124    }
125}