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            Err(crate::X402Error::config(
171                "HTTP/3 support is not enabled. Compile with 'http3' feature flag.".to_string(),
172            ))
173        }
174    }
175}
176
177/// Convenience function to create a server builder
178pub fn create_server(router: Router) -> ServerBuilder {
179    ServerBuilder::new(router)
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_server_config_default() {
188        let config = ServerConfig::default();
189        assert_eq!(config.bind_addr, "0.0.0.0:8080");
190        assert_eq!(config.protocol, HttpProtocol::Http1);
191    }
192
193    #[test]
194    fn test_server_config_new() {
195        let config = ServerConfig::new("127.0.0.1:3000", HttpProtocol::Http3);
196        assert_eq!(config.bind_addr, "127.0.0.1:3000");
197        assert_eq!(config.protocol, HttpProtocol::Http3);
198    }
199
200    #[tokio::test]
201    async fn test_server_builder() {
202        let router = Router::new();
203
204        // Test default (HTTP/1.1)
205        let builder = ServerBuilder::new(router.clone()).bind("127.0.0.1:0");
206        assert_eq!(builder.config.bind_addr, "127.0.0.1:0");
207        assert_eq!(builder.config.protocol, HttpProtocol::Http1);
208
209        // Test HTTP/2
210        let builder = ServerBuilder::new(router.clone())
211            .bind("127.0.0.1:0")
212            .version(2);
213        assert_eq!(builder.config.protocol, HttpProtocol::Http2);
214
215        // Test HTTP/3
216        let builder = ServerBuilder::new(router).bind("127.0.0.1:0").version(3);
217        assert_eq!(builder.config.protocol, HttpProtocol::Http3);
218    }
219}