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