use axum::Router;
use tokio::net::TcpListener;
use tower::ServiceBuilder;
use tower_http::{compression::CompressionLayer, cors::CorsLayer, trace::TraceLayer};
use tracing::{Level, info};
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
use crate::api::ApiDoc;
use crate::config::HttpConfig;
use crate::error::RuntimeError;
use crate::handlers;
pub struct HttpServer {
config: HttpConfig,
}
impl HttpServer {
pub fn new(config: HttpConfig) -> Self {
Self { config }
}
pub async fn start(self) -> Result<(), RuntimeError> {
let addr = self.config.bind;
info!(%addr, "Starting HTTP server");
let app = Router::new()
.merge(handlers::router())
.merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi()))
.layer(
ServiceBuilder::new()
.layer(
TraceLayer::new_for_http()
.make_span_with(|_request: &axum::http::Request<_>| {
tracing::span!(
Level::INFO,
"http_request",
method = %_request.method(),
uri = %_request.uri(),
)
})
.on_response(
|_response: &axum::response::Response<_>,
latency: std::time::Duration,
_span: &tracing::Span| {
tracing::event!(
Level::INFO,
latency_ms = latency.as_millis(),
status = %_response.status(),
"HTTP response"
);
},
),
)
.layer(CompressionLayer::new())
.layer(CorsLayer::permissive()),
);
let listener = TcpListener::bind(addr).await?;
info!(%addr, "HTTP server listening");
axum::serve(listener, app).await?;
Ok(())
}
}