rust_x402/
server.rs

1//! Unified HTTP server abstractions for x402
2//!
3//! This module provides a trait-based abstraction for creating HTTP servers
4//! that works across different HTTP protocols (HTTP/1.1, HTTP/2, and HTTP/3).
5
6use crate::Result;
7use axum::Router;
8
9/// Configuration for HTTP server binding
10#[derive(Debug, Clone)]
11pub struct ServerConfig {
12    /// Bind address (e.g., "0.0.0.0:8080")
13    pub bind_addr: String,
14    /// Protocol version to use
15    pub protocol: HttpProtocol,
16}
17
18/// HTTP protocol versions
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum HttpProtocol {
21    /// HTTP/1.1 over TCP
22    Http1,
23    /// HTTP/2 over TCP (with TLS)
24    Http2,
25    /// HTTP/3 over QUIC/UDP
26    Http3,
27}
28
29impl Default for ServerConfig {
30    fn default() -> Self {
31        Self {
32            bind_addr: "0.0.0.0:8080".to_string(),
33            protocol: HttpProtocol::Http1,
34        }
35    }
36}
37
38impl ServerConfig {
39    /// Create a new server config
40    pub fn new(bind_addr: impl Into<String>, protocol: HttpProtocol) -> Self {
41        Self {
42            bind_addr: bind_addr.into(),
43            protocol,
44        }
45    }
46}
47
48/// Trait for creating and starting HTTP servers
49#[async_trait::async_trait]
50pub trait HttpServer {
51    /// Start the server with the given router and config
52    async fn serve(router: Router, config: ServerConfig) -> Result<()>;
53}
54
55/// Unified server builder
56#[derive(Debug)]
57pub struct ServerBuilder {
58    router: Router,
59    config: ServerConfig,
60}
61
62impl ServerBuilder {
63    /// Create a new server builder
64    pub fn new(router: Router) -> Self {
65        Self {
66            router,
67            config: ServerConfig::default(),
68        }
69    }
70
71    /// Set the bind address
72    pub fn bind(mut self, addr: impl Into<String>) -> Self {
73        self.config.bind_addr = addr.into();
74        self
75    }
76
77    /// Set the HTTP protocol version
78    pub fn version(mut self, version: u8) -> Self {
79        self.config.protocol = match version {
80            1 => HttpProtocol::Http1,
81            2 => HttpProtocol::Http2,
82            3 => HttpProtocol::Http3,
83            _ => {
84                tracing::warn!("Invalid HTTP version {}, defaulting to HTTP/1.1", version);
85                HttpProtocol::Http1
86            }
87        };
88        self
89    }
90
91    /// Start the server
92    pub async fn serve(self) -> Result<()> {
93        match self.config.protocol {
94            HttpProtocol::Http1 => Http1Server::serve(self.router, self.config).await,
95            HttpProtocol::Http2 => Http2Server::serve(self.router, self.config).await,
96            HttpProtocol::Http3 => Http3Server::serve(self.router, self.config).await,
97        }
98    }
99}
100
101/// HTTP/1.1 server implementation
102pub struct Http1Server;
103
104#[async_trait::async_trait]
105impl HttpServer for Http1Server {
106    async fn serve(router: Router, config: ServerConfig) -> Result<()> {
107        let listener = tokio::net::TcpListener::bind(&config.bind_addr)
108            .await
109            .map_err(|e| {
110                crate::X402Error::config(format!("Failed to bind to {}: {}", config.bind_addr, e))
111            })?;
112
113        tracing::info!(
114            "🚀 HTTP/1.1 server listening on http://{}",
115            config.bind_addr
116        );
117
118        axum::serve(listener, router)
119            .await
120            .map_err(|e| crate::X402Error::config(format!("Server error: {}", e)))?;
121
122        Ok(())
123    }
124}
125
126/// HTTP/2 server implementation
127pub struct Http2Server;
128
129#[async_trait::async_trait]
130impl HttpServer for Http2Server {
131    async fn serve(router: Router, config: ServerConfig) -> Result<()> {
132        // HTTP/2 support is handled by Axum automatically with TLS
133        // This is a fallback to HTTP/1.1 if TLS is not configured
134        let listener = tokio::net::TcpListener::bind(&config.bind_addr)
135            .await
136            .map_err(|e| {
137                crate::X402Error::config(format!("Failed to bind to {}: {}", config.bind_addr, e))
138            })?;
139
140        tracing::info!(
141            "🚀 HTTP/2 server listening on https://{} (with TLS)",
142            config.bind_addr
143        );
144        tracing::warn!("HTTP/2 requires TLS configuration. Consider using axum with TLS support.");
145
146        axum::serve(listener, router)
147            .await
148            .map_err(|e| crate::X402Error::config(format!("Server error: {}", e)))?;
149
150        Ok(())
151    }
152}
153
154/// HTTP/3 server implementation
155pub struct Http3Server;
156
157#[async_trait::async_trait]
158impl HttpServer for Http3Server {
159    async fn serve(router: Router, config: ServerConfig) -> Result<()> {
160        #[cfg(feature = "http3")]
161        {
162            use crate::http3::Http3Config;
163
164            let http3_config = Http3Config::new(&config.bind_addr);
165            crate::http3::create_http3_server(http3_config, router).await
166        }
167
168        #[cfg(not(feature = "http3"))]
169        {
170            let _ = (router, config); // Suppress unused variable warnings when http3 feature is disabled
171            Err(crate::X402Error::config(
172                "HTTP/3 support is not enabled. Compile with 'http3' feature flag.".to_string(),
173            ))
174        }
175    }
176}
177
178/// Convenience function to create a server builder
179pub fn create_server(router: Router) -> ServerBuilder {
180    ServerBuilder::new(router)
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn test_server_config_default() {
189        let config = ServerConfig::default();
190        assert_eq!(config.bind_addr, "0.0.0.0:8080");
191        assert_eq!(config.protocol, HttpProtocol::Http1);
192    }
193
194    #[test]
195    fn test_server_config_new() {
196        let config = ServerConfig::new("127.0.0.1:3000", HttpProtocol::Http3);
197        assert_eq!(config.bind_addr, "127.0.0.1:3000");
198        assert_eq!(config.protocol, HttpProtocol::Http3);
199    }
200
201    #[tokio::test]
202    async fn test_server_builder() {
203        let router = Router::new();
204
205        // Test default (HTTP/1.1)
206        let builder = ServerBuilder::new(router.clone()).bind("127.0.0.1:0");
207        assert_eq!(builder.config.bind_addr, "127.0.0.1:0");
208        assert_eq!(builder.config.protocol, HttpProtocol::Http1);
209
210        // Test HTTP/2
211        let builder = ServerBuilder::new(router.clone())
212            .bind("127.0.0.1:0")
213            .version(2);
214        assert_eq!(builder.config.protocol, HttpProtocol::Http2);
215
216        // Test HTTP/3
217        let builder = ServerBuilder::new(router).bind("127.0.0.1:0").version(3);
218        assert_eq!(builder.config.protocol, HttpProtocol::Http3);
219    }
220}