mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-26 15:40:18 +00:00
Replace the dual-server architecture (HttpChannel + WasmChannelServer both competing for port 8080) with a single WebhookServer that composes route fragments from all sources. Channels define routes but never spawn servers. - Add WebhookServer struct that collects Router fragments and binds one listener - Extract routes() from HttpChannel, remove server-spawning from start/shutdown - Delete WasmChannelServer (keep WasmChannelRouter and route builder) - Rewire main.rs to compose all webhook routes into one server Co-Authored-By: Claude Opus 4.6 <[email protected]>
93 lines
2.8 KiB
Rust
93 lines
2.8 KiB
Rust
//! Unified HTTP server for all webhook routes.
|
|
//!
|
|
//! Composes route fragments from HttpChannel, WASM channel router, etc.
|
|
//! into a single axum server. Channels define routes but never spawn servers.
|
|
|
|
use std::net::SocketAddr;
|
|
|
|
use axum::Router;
|
|
use tokio::sync::oneshot;
|
|
use tokio::task::JoinHandle;
|
|
|
|
use crate::error::ChannelError;
|
|
|
|
/// Configuration for the unified webhook server.
|
|
pub struct WebhookServerConfig {
|
|
/// Address to bind the server to.
|
|
pub addr: SocketAddr,
|
|
}
|
|
|
|
/// A single HTTP server that hosts all webhook routes.
|
|
///
|
|
/// Channels contribute route fragments via `add_routes()`, then a single
|
|
/// `start()` call binds the listener and spawns the server task.
|
|
pub struct WebhookServer {
|
|
config: WebhookServerConfig,
|
|
routes: Vec<Router>,
|
|
shutdown_tx: Option<oneshot::Sender<()>>,
|
|
handle: Option<JoinHandle<()>>,
|
|
}
|
|
|
|
impl WebhookServer {
|
|
/// Create a new webhook server with the given bind address.
|
|
pub fn new(config: WebhookServerConfig) -> Self {
|
|
Self {
|
|
config,
|
|
routes: Vec::new(),
|
|
shutdown_tx: None,
|
|
handle: None,
|
|
}
|
|
}
|
|
|
|
/// Accumulate a route fragment. Each fragment should already have its
|
|
/// state applied via `.with_state()`.
|
|
pub fn add_routes(&mut self, router: Router) {
|
|
self.routes.push(router);
|
|
}
|
|
|
|
/// Bind the listener, merge all route fragments, and spawn the server.
|
|
pub async fn start(&mut self) -> Result<(), ChannelError> {
|
|
let mut app = Router::new();
|
|
for fragment in self.routes.drain(..) {
|
|
app = app.merge(fragment);
|
|
}
|
|
|
|
let listener = tokio::net::TcpListener::bind(self.config.addr)
|
|
.await
|
|
.map_err(|e| ChannelError::StartupFailed {
|
|
name: "webhook_server".to_string(),
|
|
reason: format!("Failed to bind to {}: {}", self.config.addr, e),
|
|
})?;
|
|
|
|
tracing::info!("Webhook server listening on {}", self.config.addr);
|
|
|
|
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
|
self.shutdown_tx = Some(shutdown_tx);
|
|
|
|
let handle = tokio::spawn(async move {
|
|
if let Err(e) = axum::serve(listener, app)
|
|
.with_graceful_shutdown(async {
|
|
let _ = shutdown_rx.await;
|
|
tracing::info!("Webhook server shutting down");
|
|
})
|
|
.await
|
|
{
|
|
tracing::error!("Webhook server error: {}", e);
|
|
}
|
|
});
|
|
|
|
self.handle = Some(handle);
|
|
Ok(())
|
|
}
|
|
|
|
/// Signal graceful shutdown and wait for the server task to finish.
|
|
pub async fn shutdown(&mut self) {
|
|
if let Some(tx) = self.shutdown_tx.take() {
|
|
let _ = tx.send(());
|
|
}
|
|
if let Some(handle) = self.handle.take() {
|
|
let _ = handle.await;
|
|
}
|
|
}
|
|
}
|