diff --git a/Cargo.toml b/Cargo.toml index 38cb7476..ffbc5e26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,7 +94,7 @@ termimad = "0.34" # Channel integrations axum = { version = "0.8", features = ["ws"] } tower = "0.5" -tower-http = { version = "0.6", features = ["trace", "cors", "set-header"] } +tower-http = { version = "0.6", features = ["trace", "cors", "set-header", "catch-panic"] } # Cron scheduling for routines cron = "0.13" diff --git a/src/channels/web/server.rs b/src/channels/web/server.rs index 857aa246..241e26fc 100644 --- a/src/channels/web/server.rs +++ b/src/channels/web/server.rs @@ -629,6 +629,25 @@ pub async fn start_server( .merge(projects) .merge(protected) .layer(DefaultBodyLimit::max(10 * 1024 * 1024)) // 10 MB max request body (image uploads) + .layer(tower_http::catch_panic::CatchPanicLayer::custom( + |panic_info: Box| { + let detail = if let Some(s) = panic_info.downcast_ref::() { + s.clone() + } else if let Some(s) = panic_info.downcast_ref::<&str>() { + (*s).to_string() + } else { + "unknown panic".to_string() + }; + tracing::error!("Handler panicked: {}", detail); + axum::http::Response::builder() + .status(axum::http::StatusCode::INTERNAL_SERVER_ERROR) + .header("content-type", "text/plain") + .body(axum::body::Body::from(format!("Internal Server Error: {detail}"))) + .unwrap_or_else(|_| { + axum::http::Response::new(axum::body::Body::from("Internal Server Error")) + }) + }, + )) .layer(cors) .layer(SetResponseHeaderLayer::if_not_present( header::X_CONTENT_TYPE_OPTIONS,