From fa065ad128882755f2a988d6203b59a2ab5e38ff Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 19 Nov 2025 11:55:32 +0000 Subject: add landing page and nostr-relay-builder relay on same port --- src/http/landing.rs | 37 ++++++++++++++++++++++++++ src/http/mod.rs | 31 ++++++++++++++++++++++ src/http/websocket.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 src/http/landing.rs create mode 100644 src/http/mod.rs create mode 100644 src/http/websocket.rs (limited to 'src/http') diff --git a/src/http/landing.rs b/src/http/landing.rs new file mode 100644 index 0000000..35e49e5 --- /dev/null +++ b/src/http/landing.rs @@ -0,0 +1,37 @@ +/// Landing Page Handler +/// +/// Serves the HTML landing page or upgrades to WebSocket for Nostr relay connections. + +use actix_web::{web, HttpRequest, HttpResponse, Result}; +use nostr_relay_builder::LocalRelay; + +use crate::config::Config; + +/// Handle landing page or WebSocket upgrade +pub async fn handle( + req: HttpRequest, + stream: web::Payload, + config: web::Data, + relay: web::Data, +) -> Result { + // Check if this is a WebSocket upgrade request + if let Some(upgrade) = req.headers().get("upgrade") { + if upgrade.to_str().unwrap_or("").eq_ignore_ascii_case("websocket") { + // Delegate to WebSocket handler + return crate::http::websocket::handle(req, stream, relay).await; + } + } + + // Otherwise, serve the landing page + let html = format!( + include_str!("../../templates/landing.html"), + relay_name = config.relay_name, + relay_description = config.relay_description, + domain = config.domain, + bind_address = config.bind_address, + ); + + Ok(HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(html)) +} \ No newline at end of file diff --git a/src/http/mod.rs b/src/http/mod.rs new file mode 100644 index 0000000..286e8ff --- /dev/null +++ b/src/http/mod.rs @@ -0,0 +1,31 @@ +/// HTTP Server Module +/// +/// Provides actix-web HTTP server with WebSocket upgrade support for the Nostr relay. + +pub mod landing; +pub mod websocket; + +use actix_web::{middleware, web, App, HttpServer}; +use nostr_relay_builder::LocalRelay; + +use crate::config::Config; + +/// Start the HTTP server with integrated Nostr relay +pub async fn run_server(config: Config, relay: LocalRelay) -> anyhow::Result<()> { + let bind_addr = config.bind_address.clone(); + + tracing::info!("Starting HTTP server on {}", bind_addr); + + HttpServer::new(move || { + App::new() + .app_data(web::Data::new(config.clone())) + .app_data(web::Data::new(relay.clone())) + .wrap(middleware::Logger::default()) + .route("/", web::get().to(landing::handle)) + }) + .bind(&bind_addr)? + .run() + .await?; + + Ok(()) +} \ No newline at end of file diff --git a/src/http/websocket.rs b/src/http/websocket.rs new file mode 100644 index 0000000..7af847a --- /dev/null +++ b/src/http/websocket.rs @@ -0,0 +1,73 @@ +/// WebSocket Handler +/// +/// Handles WebSocket upgrade requests and passes connections to the Nostr relay. + +use actix_web::{web, HttpRequest, HttpResponse, Result, Error}; +use actix_ws::Message; +use futures_util::StreamExt; +use nostr_relay_builder::LocalRelay; + +/// Handle WebSocket upgrade and relay connection +pub async fn handle( + req: HttpRequest, + stream: web::Payload, + relay: web::Data, +) -> Result { + let (response, mut session, mut msg_stream) = actix_ws::handle(&req, stream)?; + + let peer_addr = req.peer_addr() + .unwrap_or_else(|| "0.0.0.0:0".parse().unwrap()); + + tracing::debug!("WebSocket connection from {}", peer_addr); + + // Spawn task to handle the WebSocket connection + // TODO: Will use relay.take_connection() for full Nostr relay integration + let _relay = relay.get_ref().clone(); + actix_web::rt::spawn(async move { + // Create a channel to communicate between actix-ws and relay + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); + + // Spawn task to send messages from relay to client + let mut session_clone = session.clone(); + actix_web::rt::spawn(async move { + while let Some(msg) = rx.recv().await { + if session_clone.text(msg).await.is_err() { + break; + } + } + }); + + // Handle incoming messages from client + while let Some(Ok(msg)) = msg_stream.next().await { + match msg { + Message::Text(text) => { + // For now, just echo back - will integrate with relay in next phase + tracing::debug!("Received text message: {}", text); + if let Err(e) = tx.send(text.to_string()) { + tracing::error!("Failed to send message: {}", e); + break; + } + } + Message::Binary(_) => { + tracing::warn!("Received unexpected binary message"); + } + Message::Close(_) => { + tracing::debug!("Client closed connection"); + break; + } + Message::Ping(bytes) => { + if session.pong(&bytes).await.is_err() { + break; + } + } + Message::Pong(_) => {} + Message::Continuation(_) => {} + Message::Nop => {} + } + } + + tracing::debug!("WebSocket connection closed for {}", peer_addr); + }); + + Ok(response) +} \ No newline at end of file -- cgit v1.2.3