diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-19 11:55:32 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-19 15:43:29 +0000 |
| commit | fa065ad128882755f2a988d6203b59a2ab5e38ff (patch) | |
| tree | e8326de70a6e6ea56b5bf4250e0a00a3cda4afed /src/http | |
| parent | 98c6fa4bfa897ff0b8f9c95ea698d4d065b5e9f3 (diff) | |
add landing page and nostr-relay-builder relay on same port
Diffstat (limited to 'src/http')
| -rw-r--r-- | src/http/landing.rs | 37 | ||||
| -rw-r--r-- | src/http/mod.rs | 31 | ||||
| -rw-r--r-- | src/http/websocket.rs | 73 |
3 files changed, 141 insertions, 0 deletions
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 @@ | |||
| 1 | /// Landing Page Handler | ||
| 2 | /// | ||
| 3 | /// Serves the HTML landing page or upgrades to WebSocket for Nostr relay connections. | ||
| 4 | |||
| 5 | use actix_web::{web, HttpRequest, HttpResponse, Result}; | ||
| 6 | use nostr_relay_builder::LocalRelay; | ||
| 7 | |||
| 8 | use crate::config::Config; | ||
| 9 | |||
| 10 | /// Handle landing page or WebSocket upgrade | ||
| 11 | pub async fn handle( | ||
| 12 | req: HttpRequest, | ||
| 13 | stream: web::Payload, | ||
| 14 | config: web::Data<Config>, | ||
| 15 | relay: web::Data<LocalRelay>, | ||
| 16 | ) -> Result<HttpResponse> { | ||
| 17 | // Check if this is a WebSocket upgrade request | ||
| 18 | if let Some(upgrade) = req.headers().get("upgrade") { | ||
| 19 | if upgrade.to_str().unwrap_or("").eq_ignore_ascii_case("websocket") { | ||
| 20 | // Delegate to WebSocket handler | ||
| 21 | return crate::http::websocket::handle(req, stream, relay).await; | ||
| 22 | } | ||
| 23 | } | ||
| 24 | |||
| 25 | // Otherwise, serve the landing page | ||
| 26 | let html = format!( | ||
| 27 | include_str!("../../templates/landing.html"), | ||
| 28 | relay_name = config.relay_name, | ||
| 29 | relay_description = config.relay_description, | ||
| 30 | domain = config.domain, | ||
| 31 | bind_address = config.bind_address, | ||
| 32 | ); | ||
| 33 | |||
| 34 | Ok(HttpResponse::Ok() | ||
| 35 | .content_type("text/html; charset=utf-8") | ||
| 36 | .body(html)) | ||
| 37 | } \ 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 @@ | |||
| 1 | /// HTTP Server Module | ||
| 2 | /// | ||
| 3 | /// Provides actix-web HTTP server with WebSocket upgrade support for the Nostr relay. | ||
| 4 | |||
| 5 | pub mod landing; | ||
| 6 | pub mod websocket; | ||
| 7 | |||
| 8 | use actix_web::{middleware, web, App, HttpServer}; | ||
| 9 | use nostr_relay_builder::LocalRelay; | ||
| 10 | |||
| 11 | use crate::config::Config; | ||
| 12 | |||
| 13 | /// Start the HTTP server with integrated Nostr relay | ||
| 14 | pub async fn run_server(config: Config, relay: LocalRelay) -> anyhow::Result<()> { | ||
| 15 | let bind_addr = config.bind_address.clone(); | ||
| 16 | |||
| 17 | tracing::info!("Starting HTTP server on {}", bind_addr); | ||
| 18 | |||
| 19 | HttpServer::new(move || { | ||
| 20 | App::new() | ||
| 21 | .app_data(web::Data::new(config.clone())) | ||
| 22 | .app_data(web::Data::new(relay.clone())) | ||
| 23 | .wrap(middleware::Logger::default()) | ||
| 24 | .route("/", web::get().to(landing::handle)) | ||
| 25 | }) | ||
| 26 | .bind(&bind_addr)? | ||
| 27 | .run() | ||
| 28 | .await?; | ||
| 29 | |||
| 30 | Ok(()) | ||
| 31 | } \ 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 @@ | |||
| 1 | /// WebSocket Handler | ||
| 2 | /// | ||
| 3 | /// Handles WebSocket upgrade requests and passes connections to the Nostr relay. | ||
| 4 | |||
| 5 | use actix_web::{web, HttpRequest, HttpResponse, Result, Error}; | ||
| 6 | use actix_ws::Message; | ||
| 7 | use futures_util::StreamExt; | ||
| 8 | use nostr_relay_builder::LocalRelay; | ||
| 9 | |||
| 10 | /// Handle WebSocket upgrade and relay connection | ||
| 11 | pub async fn handle( | ||
| 12 | req: HttpRequest, | ||
| 13 | stream: web::Payload, | ||
| 14 | relay: web::Data<LocalRelay>, | ||
| 15 | ) -> Result<HttpResponse, Error> { | ||
| 16 | let (response, mut session, mut msg_stream) = actix_ws::handle(&req, stream)?; | ||
| 17 | |||
| 18 | let peer_addr = req.peer_addr() | ||
| 19 | .unwrap_or_else(|| "0.0.0.0:0".parse().unwrap()); | ||
| 20 | |||
| 21 | tracing::debug!("WebSocket connection from {}", peer_addr); | ||
| 22 | |||
| 23 | // Spawn task to handle the WebSocket connection | ||
| 24 | // TODO: Will use relay.take_connection() for full Nostr relay integration | ||
| 25 | let _relay = relay.get_ref().clone(); | ||
| 26 | actix_web::rt::spawn(async move { | ||
| 27 | // Create a channel to communicate between actix-ws and relay | ||
| 28 | let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); | ||
| 29 | |||
| 30 | // Spawn task to send messages from relay to client | ||
| 31 | let mut session_clone = session.clone(); | ||
| 32 | actix_web::rt::spawn(async move { | ||
| 33 | while let Some(msg) = rx.recv().await { | ||
| 34 | if session_clone.text(msg).await.is_err() { | ||
| 35 | break; | ||
| 36 | } | ||
| 37 | } | ||
| 38 | }); | ||
| 39 | |||
| 40 | // Handle incoming messages from client | ||
| 41 | while let Some(Ok(msg)) = msg_stream.next().await { | ||
| 42 | match msg { | ||
| 43 | Message::Text(text) => { | ||
| 44 | // For now, just echo back - will integrate with relay in next phase | ||
| 45 | tracing::debug!("Received text message: {}", text); | ||
| 46 | if let Err(e) = tx.send(text.to_string()) { | ||
| 47 | tracing::error!("Failed to send message: {}", e); | ||
| 48 | break; | ||
| 49 | } | ||
| 50 | } | ||
| 51 | Message::Binary(_) => { | ||
| 52 | tracing::warn!("Received unexpected binary message"); | ||
| 53 | } | ||
| 54 | Message::Close(_) => { | ||
| 55 | tracing::debug!("Client closed connection"); | ||
| 56 | break; | ||
| 57 | } | ||
| 58 | Message::Ping(bytes) => { | ||
| 59 | if session.pong(&bytes).await.is_err() { | ||
| 60 | break; | ||
| 61 | } | ||
| 62 | } | ||
| 63 | Message::Pong(_) => {} | ||
| 64 | Message::Continuation(_) => {} | ||
| 65 | Message::Nop => {} | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | tracing::debug!("WebSocket connection closed for {}", peer_addr); | ||
| 70 | }); | ||
| 71 | |||
| 72 | Ok(response) | ||
| 73 | } \ No newline at end of file | ||