1use axum::{
2 routing::{get, post},
3 Router,
4};
5use hehe_agent::Agent;
6use tower_http::cors::{Any, CorsLayer};
7use tower_http::trace::TraceLayer;
8use tracing::info;
9
10use crate::config::ServerConfig;
11use crate::error::Result;
12use crate::routes;
13use crate::state::AppState;
14
15pub struct Server {
16 config: ServerConfig,
17 state: AppState,
18}
19
20impl Server {
21 pub fn new(config: ServerConfig, agent: Agent) -> Self {
22 Self {
23 config,
24 state: AppState::new(agent),
25 }
26 }
27
28 pub fn router(&self) -> Router {
29 let cors = CorsLayer::new()
30 .allow_origin(Any)
31 .allow_methods(Any)
32 .allow_headers(Any);
33
34 Router::new()
35 .route("/health", get(routes::health))
36 .route("/ready", get(routes::ready))
37 .route("/api/v1/chat", post(routes::chat))
38 .route("/api/v1/chat/stream", post(routes::chat_stream))
39 .layer(cors)
40 .layer(TraceLayer::new_for_http())
41 .with_state(self.state.clone())
42 }
43
44 pub async fn run(self) -> Result<()> {
45 let addr = self.config.socket_addr();
46 let listener = tokio::net::TcpListener::bind(addr).await.map_err(|e| {
47 crate::error::ServerError::internal(format!("Failed to bind to {}: {}", addr, e))
48 })?;
49
50 info!("Server listening on {}", addr);
51
52 axum::serve(listener, self.router())
53 .await
54 .map_err(|e| crate::error::ServerError::internal(e.to_string()))?;
55
56 Ok(())
57 }
58
59 pub async fn run_with_shutdown<F>(self, shutdown: F) -> Result<()>
60 where
61 F: std::future::Future<Output = ()> + Send + 'static,
62 {
63 let addr = self.config.socket_addr();
64 let listener = tokio::net::TcpListener::bind(addr).await.map_err(|e| {
65 crate::error::ServerError::internal(format!("Failed to bind to {}: {}", addr, e))
66 })?;
67
68 info!("Server listening on {}", addr);
69
70 axum::serve(listener, self.router())
71 .with_graceful_shutdown(shutdown)
72 .await
73 .map_err(|e| crate::error::ServerError::internal(e.to_string()))?;
74
75 Ok(())
76 }
77
78 pub fn config(&self) -> &ServerConfig {
79 &self.config
80 }
81
82 pub fn state(&self) -> &AppState {
83 &self.state
84 }
85}
86
87pub async fn shutdown_signal() {
88 let ctrl_c = async {
89 tokio::signal::ctrl_c()
90 .await
91 .expect("Failed to install Ctrl+C handler");
92 };
93
94 #[cfg(unix)]
95 let terminate = async {
96 tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
97 .expect("Failed to install signal handler")
98 .recv()
99 .await;
100 };
101
102 #[cfg(not(unix))]
103 let terminate = std::future::pending::<()>();
104
105 tokio::select! {
106 _ = ctrl_c => {},
107 _ = terminate => {},
108 }
109
110 info!("Shutdown signal received");
111}