diff --git a/Cargo.toml b/Cargo.toml index 43b2d91..f657803 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nostr-relay" -version = "0.1.0" +version = "0.0.2" edition = "2024" [dependencies] @@ -18,3 +18,4 @@ log = "0.4.27" env_logger = "0.11.8" uuid = { version = "1.6.2", features = ["v4"] } sqlx = { version = "0.7.1", features = ["runtime-tokio-rustls", "sqlite"] } +httparse = "1.10.1" diff --git a/src/main.rs b/src/main.rs index d2c2e71..2b1f7c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,8 @@ use tokio::{ }; use tokio_tungstenite::{WebSocketStream, accept_async, tungstenite::protocol::Message}; use uuid::Uuid; +use httparse::{Request, EMPTY_HEADER}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; #[derive(Debug, Serialize, Deserialize)] pub struct NostrEvent { @@ -63,6 +65,30 @@ pub struct Filter { pub until: Option, // Unix 时间戳,秒 pub limit: Option, // 最大返回数 } +#[derive(Serialize)] +struct Limitation { + max_event_tags: u32, + max_event_time_newer_than_now: u32, + max_event_time_older_than_now: u32, + max_filters: u32, + max_limit: u32, + max_message_length: u32, + max_subid_length: u32, + max_subscriptions: u32, + min_prefix: u32, +} + +#[derive(Serialize)] +struct ServerInfo { + contact: &'static str, + description: &'static str, + limitation: Limitation, + name: &'static str, + pubkey: &'static str, + software: &'static str, + supported_nips: Vec, + version: &'static str, +} #[tokio::main] async fn main() { @@ -76,17 +102,129 @@ async fn main() { let listener = TcpListener::bind(&addr).await.expect("Failed to bind"); info!("Listening on: {}", addr); let (tx, _) = broadcast::channel::(100); + while let Ok((stream, addr)) = listener.accept().await { let tx = tx.clone(); let rx = tx.subscribe(); let pool = pool.clone(); + tokio::spawn(async move { - handle_connection(stream, tx, rx, pool).await; + if let Err(e) = handle_connection_multiplex(stream, tx, rx, pool).await { + error!("Error handling connection: {}", e); + } }); } } -async fn handle_connection( +async fn handle_connection_multiplex( + mut stream: TcpStream, + tx: broadcast::Sender, + rx: broadcast::Receiver, + pool: Arc, +) -> Result<(), anyhow::Error> { + // 分配足够大的缓冲区,一次读完整个头 + let mut buf = vec![0u8; 4096]; + let n = stream.peek(&mut buf).await?; + let req_bytes = &buf[..n]; + + let mut headers: [httparse::Header; 32] = [EMPTY_HEADER; 32]; + let mut req = Request::new(&mut headers); + let status = req.parse(req_bytes)?; + if status.is_partial() { + anyhow::bail!("Request headers too large"); + } + let method = req.method.unwrap_or(""); + let header_map = req + .headers + .iter() + .filter_map(|h| { + let name = h.name.to_ascii_lowercase(); + let val = std::str::from_utf8(h.value).ok()?; + Some((name, val)) + }) + .collect::>(); + + if method == "GET" + && header_map + .get("upgrade") + .map(|&v| v.eq_ignore_ascii_case("websocket")) + .unwrap_or(false) + { + handle_ws_connection(stream, tx, rx, pool).await; + }else { + if let Some(accept) = header_map.get("accept") { + if accept.contains("application/json") { + handle_http_info(stream).await?; + } else if accept.contains("text/html") { + handle_http(stream).await?; + } else { + handle_http_info(stream).await?; + } + } else { + handle_http_info(stream).await?; + } + } + + Ok(()) +} + +async fn handle_http_info(mut stream: TcpStream) -> Result<(), anyhow::Error> { + let mut buffer = vec![0; 1024]; + stream.read(&mut buffer).await?; + let info = ServerInfo { + contact: "https://www.moec.top/", + description: "Powered by laoXong.", + limitation: Limitation { + max_event_tags: 5000, + max_event_time_newer_than_now: 900, + max_event_time_older_than_now: 315576000, + max_filters: 100, + max_limit: 100, + max_message_length: 524288, + max_subid_length: 100, + max_subscriptions: 20, + min_prefix: 10, + }, + name: "A rust nostr relay by laoXong", + pubkey: "63abd4f817e39cca4e6abb6e6cf3e133bb718cf8ec28b38c1645e84d7a6190c6", + software: "https://git.moe.gift/laoxong/nostr-relay", + supported_nips: vec![1, 2, 5], + version: env!("CARGO_PKG_VERSION"), + }; + + // 序列化为 JSON + let json = serde_json::to_string(&info) + .expect("Failed to serialize server info"); + let response = format!( + "HTTP/1.1 200 OK\r\n\ + Content-Type: application/nostr+json\r\n\ + Access-Control-Allow-Origin: *\r\n\ + \r\n\ + {}", + json); + stream.write_all(response.as_bytes()).await?; + stream.flush().await?; + + Ok(()) +} + +async fn handle_http(mut stream: TcpStream) -> Result<(), anyhow::Error> { + let mut buffer = vec![0; 1024]; + stream.read(&mut buffer).await?; + let response = format!( + "HTTP/1.1 200 OK\r\n\ + Content-Type: text/html\r\n\ + Access-Control-Allow-Origin: *\r\n\ + \r\n\ + A Simple Nostr Relay by laoXong Version:{}", + env!("CARGO_PKG_VERSION")); + stream.write_all(response.as_bytes()).await?; + stream.flush().await?; + Ok(()) +} + + +async fn handle_ws_connection( stream: TcpStream, tx: broadcast::Sender, mut rx: broadcast::Receiver,