use crate::Result;
use axum::Router;
#[derive(Debug, Clone)]
pub struct ServerConfig {
pub bind_addr: String,
pub protocol: HttpProtocol,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HttpProtocol {
Http1,
Http2,
Http3,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
bind_addr: "0.0.0.0:8080".to_string(),
protocol: HttpProtocol::Http1,
}
}
}
impl ServerConfig {
pub fn new(bind_addr: impl Into<String>, protocol: HttpProtocol) -> Self {
Self {
bind_addr: bind_addr.into(),
protocol,
}
}
}
#[async_trait::async_trait]
pub trait HttpServer {
async fn serve(router: Router, config: ServerConfig) -> Result<()>;
}
#[derive(Debug)]
pub struct ServerBuilder {
router: Router,
config: ServerConfig,
}
impl ServerBuilder {
pub fn new(router: Router) -> Self {
Self {
router,
config: ServerConfig::default(),
}
}
pub fn bind(mut self, addr: impl Into<String>) -> Self {
self.config.bind_addr = addr.into();
self
}
pub fn version(mut self, version: u8) -> Self {
self.config.protocol = match version {
1 => HttpProtocol::Http1,
2 => HttpProtocol::Http2,
3 => HttpProtocol::Http3,
_ => {
tracing::warn!("Invalid HTTP version {}, defaulting to HTTP/1.1", version);
HttpProtocol::Http1
}
};
self
}
pub async fn serve(self) -> Result<()> {
match self.config.protocol {
HttpProtocol::Http1 => Http1Server::serve(self.router, self.config).await,
HttpProtocol::Http2 => Http2Server::serve(self.router, self.config).await,
HttpProtocol::Http3 => Http3Server::serve(self.router, self.config).await,
}
}
}
pub struct Http1Server;
#[async_trait::async_trait]
impl HttpServer for Http1Server {
async fn serve(router: Router, config: ServerConfig) -> Result<()> {
let listener = tokio::net::TcpListener::bind(&config.bind_addr)
.await
.map_err(|e| {
crate::X402Error::config(format!("Failed to bind to {}: {}", config.bind_addr, e))
})?;
tracing::info!(
"🚀 HTTP/1.1 server listening on http://{}",
config.bind_addr
);
axum::serve(listener, router)
.await
.map_err(|e| crate::X402Error::config(format!("Server error: {}", e)))?;
Ok(())
}
}
pub struct Http2Server;
#[async_trait::async_trait]
impl HttpServer for Http2Server {
async fn serve(router: Router, config: ServerConfig) -> Result<()> {
let listener = tokio::net::TcpListener::bind(&config.bind_addr)
.await
.map_err(|e| {
crate::X402Error::config(format!("Failed to bind to {}: {}", config.bind_addr, e))
})?;
tracing::info!(
"🚀 HTTP/2 server listening on https://{} (with TLS)",
config.bind_addr
);
tracing::warn!("HTTP/2 requires TLS configuration. Consider using axum with TLS support.");
axum::serve(listener, router)
.await
.map_err(|e| crate::X402Error::config(format!("Server error: {}", e)))?;
Ok(())
}
}
pub struct Http3Server;
#[async_trait::async_trait]
impl HttpServer for Http3Server {
async fn serve(router: Router, config: ServerConfig) -> Result<()> {
#[cfg(feature = "http3")]
{
use crate::http3::Http3Config;
let http3_config = Http3Config::new(&config.bind_addr);
crate::http3::create_http3_server(http3_config, router).await
}
#[cfg(not(feature = "http3"))]
{
let _ = (router, config); Err(crate::X402Error::config(
"HTTP/3 support is not enabled. Compile with 'http3' feature flag.".to_string(),
))
}
}
}
pub fn create_server(router: Router) -> ServerBuilder {
ServerBuilder::new(router)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_server_config_default() {
let config = ServerConfig::default();
assert_eq!(config.bind_addr, "0.0.0.0:8080");
assert_eq!(config.protocol, HttpProtocol::Http1);
}
#[test]
fn test_server_config_new() {
let config = ServerConfig::new("127.0.0.1:3000", HttpProtocol::Http3);
assert_eq!(config.bind_addr, "127.0.0.1:3000");
assert_eq!(config.protocol, HttpProtocol::Http3);
}
#[tokio::test]
async fn test_server_builder() {
let router = Router::new();
let builder = ServerBuilder::new(router.clone()).bind("127.0.0.1:0");
assert_eq!(builder.config.bind_addr, "127.0.0.1:0");
assert_eq!(builder.config.protocol, HttpProtocol::Http1);
let builder = ServerBuilder::new(router.clone())
.bind("127.0.0.1:0")
.version(2);
assert_eq!(builder.config.protocol, HttpProtocol::Http2);
let builder = ServerBuilder::new(router).bind("127.0.0.1:0").version(3);
assert_eq!(builder.config.protocol, HttpProtocol::Http3);
}
}